summaryrefslogtreecommitdiff
path: root/lisp/minibuffer.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/minibuffer.el')
-rw-r--r--lisp/minibuffer.el1029
1 files changed, 808 insertions, 221 deletions
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index a9e3ec937f9..0a844c538b4 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -150,21 +150,29 @@ The metadata of a completion table should be constant between two boundaries."
minibuffer-completion-table
minibuffer-completion-predicate))
+(defun completion--metadata-get-1 (metadata prop)
+ (or (alist-get prop metadata)
+ (plist-get completion-extra-properties
+ ;; Cache the keyword
+ (or (get prop 'completion-extra-properties--keyword)
+ (put prop 'completion-extra-properties--keyword
+ (intern (concat ":" (symbol-name prop))))))))
+
(defun completion-metadata-get (metadata prop)
- (cdr (assq prop metadata)))
-
-(defun completion--some (fun xs)
- "Apply FUN to each element of XS in turn.
-Return the first non-nil returned value.
-Like CL's `some'."
- (let ((firsterror nil)
- res)
- (while (and (not res) xs)
- (condition-case-unless-debug err
- (setq res (funcall fun (pop xs)))
- (error (unless firsterror (setq firsterror err)) nil)))
- (or res
- (if firsterror (signal (car firsterror) (cdr firsterror))))))
+ "Get property PROP from completion METADATA.
+If the metadata specifies a completion category, the variables
+`completion-category-overrides' and
+`completion-category-defaults' take precedence for
+category-specific overrides. If the completion metadata does not
+specify the property, the `completion-extra-properties' plist is
+consulted. Note that the keys of the
+`completion-extra-properties' plist are keyword symbols, not
+plain symbols."
+ (if-let (((not (eq prop 'category)))
+ (cat (completion--metadata-get-1 metadata 'category))
+ (over (completion--category-override cat prop)))
+ (cdr over)
+ (completion--metadata-get-1 metadata prop)))
(defun complete-with-action (action collection string predicate)
"Perform completion according to ACTION.
@@ -313,7 +321,7 @@ the form (concat S2 S)."
;; Predicates are called differently depending on the nature of
;; the completion table :-(
(cond
- ((vectorp table) ;Obarray.
+ ((obarrayp table)
(lambda (sym) (funcall pred (concat prefix (symbol-name sym)))))
((hash-table-p table)
(lambda (s _v) (funcall pred (concat prefix s))))
@@ -426,9 +434,9 @@ obeys predicates."
;; is returned by TABLE2 (because TABLE1 returned an empty list).
;; Same potential problem if any of the tables use quoting.
(lambda (string pred action)
- (completion--some (lambda (table)
- (complete-with-action action table string pred))
- tables)))
+ (seq-some (lambda (table)
+ (complete-with-action action table string pred))
+ tables)))
(defun completion-table-merge (&rest tables)
"Create a completion table that collects completions from all TABLES."
@@ -451,9 +459,9 @@ obeys predicates."
(all-completions string table pred))
tables)))
(t
- (completion--some (lambda (table)
- (complete-with-action action table string pred))
- tables)))))
+ (seq-some (lambda (table)
+ (complete-with-action action table string pred))
+ tables)))))
(defun completion-table-with-quoting (table unquote requote)
;; A difficult part of completion-with-quoting is to map positions in the
@@ -690,6 +698,17 @@ for use at QPOS."
'completions-common-part)
qprefix))))
(qcompletion (concat qprefix qnew)))
+ ;; Some completion tables (including this one) pass
+ ;; along necessary information as text properties
+ ;; on the first character of the completion. Make
+ ;; sure the quoted completion has these properties
+ ;; too.
+ (add-text-properties 0 1 (text-properties-at 0 completion)
+ qcompletion)
+ ;; Attach unquoted completion string, which is needed
+ ;; to score the completion in `completion--flex-score'.
+ (put-text-property 0 1 'completion--unquoted
+ completion qcompletion)
;; FIXME: Similarly here, Cygwin's mapping trips this
;; assertion.
;;(cl-assert
@@ -963,6 +982,8 @@ is at its default value `grow-only'."
(reverse multi-message-list)
multi-message-separator)))
+(defvar touch-screen-current-tool)
+
(defun clear-minibuffer-message ()
"Clear message temporarily shown in the minibuffer.
Intended to be called via `clear-message-function'."
@@ -973,10 +994,16 @@ Intended to be called via `clear-message-function'."
(when (overlayp minibuffer-message-overlay)
(delete-overlay minibuffer-message-overlay)
(setq minibuffer-message-overlay nil)))
-
- ;; Return nil telling the caller that the message
- ;; should be also handled by the caller.
- nil)
+ ;; Don't clear the message if touch screen drag-to-select is in
+ ;; progress, because a preview message might currently be displayed
+ ;; in the echo area. FIXME: find some way to place this in
+ ;; touch-screen.el.
+ (if (and (bound-and-true-p touch-screen-preview-select)
+ (eq (nth 3 touch-screen-current-tool) 'drag))
+ 'dont-clear-message
+ ;; Return nil telling the caller that the message
+ ;; should be also handled by the caller.
+ nil))
(setq clear-message-function 'clear-minibuffer-message)
@@ -1117,11 +1144,7 @@ and DOC describes the way this style of completion works.")
The available styles are listed in `completion-styles-alist'.
Note that `completion-category-overrides' may override these
-styles for specific categories, such as files, buffers, etc.
-
-Note that Tramp host name completion (e.g., \"/ssh:ho<TAB>\")
-currently doesn't work if this list doesn't contain at least one
-of `basic', `emacs22' or `emacs21'."
+styles for specific categories, such as files, buffers, etc."
:type completion--styles-type
:version "23.1")
@@ -1133,23 +1156,42 @@ of `basic', `emacs22' or `emacs21'."
(project-file (styles . (substring)))
(xref-location (styles . (substring)))
(info-menu (styles . (basic substring)))
- (symbol-help (styles . (basic shorthand substring))))
+ (symbol-help (styles . (basic shorthand substring)))
+ (calendar-month (display-sort-function . identity)))
"Default settings for specific completion categories.
+
Each entry has the shape (CATEGORY . ALIST) where ALIST is
an association list that can specify properties such as:
- `styles': the list of `completion-styles' to use for that category.
- `cycle': the `completion-cycle-threshold' to use for that category.
+- `cycle-sort-function': function to sort entries when cycling.
+- `display-sort-function': function to sort entries in *Completions*.
+- `group-function': function for grouping the completion candidates.
+- `annotation-function': function to add annotations in *Completions*.
+- `affixation-function': function to prepend/append a prefix/suffix.
+
Categories are symbols such as `buffer' and `file', used when
completing buffer and file names, respectively.
Also see `completion-category-overrides'.")
(defcustom completion-category-overrides nil
- "List of category-specific user overrides for completion styles.
+ "List of category-specific user overrides for completion metadata.
+
Each override has the shape (CATEGORY . ALIST) where ALIST is
an association list that can specify properties such as:
- `styles': the list of `completion-styles' to use for that category.
- `cycle': the `completion-cycle-threshold' to use for that category.
+- `cycle-sort-function': function to sort entries when cycling.
+- `display-sort-function': nil means to use either the sorting
+function from metadata, or if that is nil, fall back to `completions-sort';
+`identity' disables sorting and keeps the original order; and other
+possible values are the same as in `completions-sort'.
+- `group-function': function for grouping the completion candidates.
+- `annotation-function': function to add annotations in *Completions*.
+- `affixation-function': function to prepend/append a prefix/suffix.
+See more description of metadata in `completion-metadata'.
+
Categories are symbols such as `buffer' and `file', used when
completing buffer and file names, respectively.
@@ -1169,7 +1211,33 @@ overrides the default specified in `completion-category-defaults'."
,completion--styles-type)
(cons :tag "Completion Cycling"
(const :tag "Select one value from the menu." cycle)
- ,completion--cycling-threshold-type))))
+ ,completion--cycling-threshold-type)
+ (cons :tag "Cycle Sorting"
+ (const :tag "Select one value from the menu."
+ cycle-sort-function)
+ (choice (function :tag "Custom function")))
+ (cons :tag "Completion Sorting"
+ (const :tag "Select one value from the menu."
+ display-sort-function)
+ (choice (const :tag "Use default" nil)
+ (const :tag "No sorting" identity)
+ (const :tag "Alphabetical sorting"
+ minibuffer-sort-alphabetically)
+ (const :tag "Historical sorting"
+ minibuffer-sort-by-history)
+ (function :tag "Custom function")))
+ (cons :tag "Completion Groups"
+ (const :tag "Select one value from the menu."
+ group-function)
+ (choice (function :tag "Custom function")))
+ (cons :tag "Completion Annotation"
+ (const :tag "Select one value from the menu."
+ annotation-function)
+ (choice (function :tag "Custom function")))
+ (cons :tag "Completion Affixation"
+ (const :tag "Select one value from the menu."
+ affixation-function)
+ (choice (function :tag "Custom function"))))))
(defun completion--category-override (category tag)
(or (assq tag (cdr (assq category completion-category-overrides)))
@@ -1214,7 +1282,7 @@ overrides the default specified in `completion-category-defaults'."
(cl-assert (<= point (length string)))
(pop new))))
(result-and-style
- (completion--some
+ (seq-some
(lambda (style)
(let ((probe (funcall
(or (nth n (assq style completion-styles-alist))
@@ -1245,6 +1313,7 @@ Only the elements of table that satisfy predicate PRED are considered.
POINT is the position of point within STRING.
The return value is a list of completions and may contain the base-size
in the last `cdr'."
+ (setq completion-lazy-hilit-fn nil)
;; FIXME: We need to additionally return the info needed for the
;; second part of completion-base-position.
(completion--nth-completion 2 string table pred point metadata))
@@ -1313,14 +1382,29 @@ completion candidates than this number."
(defcustom completions-sort 'alphabetical
"Sort candidates in the *Completions* buffer.
-The value can be nil to disable sorting, `alphabetical' for
-alphabetical sorting or a custom sorting function. The sorting
-function takes and returns a list of completion candidate
-strings."
+Completion candidates in the *Completions* buffer are sorted
+depending on the value.
+
+If it's nil, sorting is disabled.
+If it's the symbol `alphabetical', candidates are sorted by
+`minibuffer-sort-alphabetically'.
+If it's the symbol `historical', candidates are sorted by
+`minibuffer-sort-by-history', which first sorts alphabetically,
+and then rearranges the order according to the order of the
+candidates in the minibuffer history.
+If it's a function, the function is called to sort the candidates.
+The sorting function takes a list of completion candidate
+strings, which it may modify; it should return a sorted list,
+which may be the same.
+
+If the completion-specific metadata provides a
+`display-sort-function', that function overrides the value of
+this variable."
:type '(choice (const :tag "No sorting" nil)
(const :tag "Alphabetical sorting" alphabetical)
+ (const :tag "Historical sorting" historical)
(function :tag "Custom function"))
- :version "29.1")
+ :version "30.1")
(defcustom completions-group nil
"Enable grouping of completion candidates in the *Completions* buffer.
@@ -1567,11 +1651,12 @@ scroll the window of possible completions."
(t (prog1 (pcase (completion--do-completion beg end)
(#b000 nil)
(_ t))
- (when (and (eq completion-auto-select t)
- (window-live-p minibuffer-scroll-window)
- (eq t (frame-visible-p (window-frame minibuffer-scroll-window))))
- ;; When the completion list window was displayed, select it.
- (switch-to-completions))))))
+ (if (window-live-p minibuffer-scroll-window)
+ (and (eq completion-auto-select t)
+ (eq t (frame-visible-p (window-frame minibuffer-scroll-window)))
+ ;; When the completion list window was displayed, select it.
+ (switch-to-completions))
+ (completion-in-region-mode -1))))))
(defun completion--cache-all-sorted-completions (beg end comps)
(add-hook 'after-change-functions
@@ -1645,6 +1730,44 @@ Remove completion BASE prefix string from history elements."
(substring c base-size)))
hist)))))
+(defun minibuffer-sort-alphabetically (completions)
+ "Sort COMPLETIONS alphabetically.
+
+COMPLETIONS are sorted alphabetically by `string-lessp'.
+
+This is a suitable function to use for `completions-sort' or to
+include as `display-sort-function' in completion metadata."
+ (sort completions #'string-lessp))
+
+(defvar minibuffer-completion-base nil
+ "The base for the current completion.
+
+This is the part of the current minibuffer input which comes
+before the current completion field, as determined by
+`completion-boundaries'. This is primarily relevant for file
+names, where this is the directory component of the file name.")
+
+(defun minibuffer-sort-by-history (completions)
+ "Sort COMPLETIONS by their position in `minibuffer-history-variable'.
+
+COMPLETIONS are sorted first by `minibuffer-sort-alphbetically',
+then any elements occurring in the minibuffer history list are
+moved to the front based on the chronological order they occur in
+the history. If a history variable hasn't been specified for
+this call of `completing-read', COMPLETIONS are sorted only by
+`minibuffer-sort-alphbetically'.
+
+This is a suitable function to use for `completions-sort' or to
+include as `display-sort-function' in completion metadata."
+ (let ((alphabetized (sort completions #'string-lessp)))
+ ;; Only use history when it's specific to these completions.
+ (if (eq minibuffer-history-variable
+ (default-value minibuffer-history-variable))
+ alphabetized
+ (minibuffer--sort-by-position
+ (minibuffer--sort-preprocess-history minibuffer-completion-base)
+ alphabetized))))
+
(defun minibuffer--group-by (group-fun sort-fun elems)
"Group ELEMS by GROUP-FUN and sort groups by SORT-FUN."
(let ((groups))
@@ -2311,8 +2434,11 @@ candidates."
(with-current-buffer standard-output
(goto-char (point-max))
- (when completions-header-format
- (insert (format completions-header-format (length completions))))
+ (if completions-header-format
+ (insert (format completions-header-format (length completions)))
+ (unless completion-show-help
+ ;; Ensure beginning-of-buffer isn't a completion.
+ (insert (propertize "\n" 'face '(:height 0)))))
(completion--insert-strings completions group-fun)))
(run-hooks 'completion-setup-hook)
@@ -2322,6 +2448,9 @@ candidates."
"Property list of extra properties of the current completion job.
These include:
+`:category': the kind of objects returned by `all-completions'.
+ Used by `completion-category-overrides'.
+
`:annotation-function': Function to annotate the completions buffer.
The function must accept one argument, a completion string,
and return either nil or a string which is to be displayed
@@ -2337,6 +2466,15 @@ These include:
`:annotation-function' when both are provided, so only this
function is used.
+`:group-function': Function for grouping the completion candidates.
+
+`:display-sort-function': Function to sort entries in *Completions*.
+
+`:cycle-sort-function': Function to sort entries when cycling.
+
+See more information about these functions above
+in `completion-metadata'.
+
`:exit-function': Function to run after completion is performed.
The function must accept two arguments, STRING and STATUS.
@@ -2379,6 +2517,36 @@ These include:
(resize-temp-buffer-window win))
(fit-window-to-buffer win completions-max-height)))
+(defcustom completion-auto-deselect t
+ "If non-nil, deselect current completion candidate when you type in minibuffer.
+
+A non-nil value means that after typing at the minibuffer prompt,
+any completion candidate highlighted in *Completions* window (to
+indicate that it is the selected candidate) will be un-highlighted,
+and point in the *Completions* window will be moved off such a candidate.
+This means that `RET' (`minibuffer-choose-completion-or-exit') will exit
+the minubuffer with the minibuffer's current contents, instead of the
+selected completion candidate."
+ :type '(choice (const :tag "Candidates in *Completions* stay selected as you type" nil)
+ (const :tag "Typing deselects any completion candidate in *Completions*" t))
+ :version "30.1")
+
+(defun completions--deselect ()
+ "If point is in a completion candidate, move to just after the end of it.
+
+The candidate will still be chosen by `choose-completion' unless
+`choose-completion-deselect-if-after' is non-nil."
+ (when (get-text-property (point) 'completion--string)
+ (goto-char (or (next-single-property-change (point) 'completion--string)
+ (point-max)))))
+
+(defun completions--after-change (_start _end _old-len)
+ "Update displayed *Completions* buffer after change in buffer contents."
+ (when completion-auto-deselect
+ (when-let (window (get-buffer-window "*Completions*" 0))
+ (with-selected-window window
+ (completions--deselect)))))
+
(defun minibuffer-completion-help (&optional start end)
"Display a list of possible completions of the current minibuffer contents."
(interactive)
@@ -2401,6 +2569,7 @@ These include:
;; If there are no completions, or if the current input is already
;; the sole completion, then hide (previous&stale) completions.
(minibuffer-hide-completions)
+ (remove-hook 'after-change-functions #'completions--after-change t)
(if completions
(completion--message "Sole completion")
(unless completion-fail-discreetly
@@ -2410,20 +2579,26 @@ These include:
(let* ((last (last completions))
(base-size (or (cdr last) 0))
(prefix (unless (zerop base-size) (substring string 0 base-size)))
+ (minibuffer-completion-base (substring string 0 base-size))
(base-prefix (buffer-substring (minibuffer--completion-prompt-end)
(+ start base-size)))
- (base-suffix (buffer-substring (point) (point-max)))
+ (base-suffix
+ (if (or (eq (alist-get 'category (cdr md)) 'file)
+ completion-in-region-mode-predicate)
+ (buffer-substring
+ (save-excursion
+ (if completion-in-region-mode-predicate
+ (point)
+ (or (search-forward "/" nil t) (point-max))))
+ (point-max))
+ ""))
(all-md (completion--metadata (buffer-substring-no-properties
start (point))
base-size md
minibuffer-completion-table
minibuffer-completion-predicate))
- (ann-fun (or (completion-metadata-get all-md 'annotation-function)
- (plist-get completion-extra-properties
- :annotation-function)))
- (aff-fun (or (completion-metadata-get all-md 'affixation-function)
- (plist-get completion-extra-properties
- :affixation-function)))
+ (ann-fun (completion-metadata-get all-md 'annotation-function))
+ (aff-fun (completion-metadata-get all-md 'affixation-function))
(sort-fun (completion-metadata-get all-md 'display-sort-function))
(group-fun (completion-metadata-get all-md 'group-function))
(mainbuf (current-buffer))
@@ -2452,6 +2627,8 @@ These include:
(body-function
. ,#'(lambda (_window)
(with-current-buffer mainbuf
+ (when completion-auto-deselect
+ (add-hook 'after-change-functions #'completions--after-change nil t))
;; Remove the base-size tail because `sort' requires a properly
;; nil-terminated list.
(when last (setcdr last nil))
@@ -2465,7 +2642,8 @@ These include:
(funcall sort-fun completions)
(pcase completions-sort
('nil completions)
- ('alphabetical (sort completions #'string-lessp))
+ ('alphabetical (minibuffer-sort-alphabetically completions))
+ ('historical (minibuffer-sort-by-history completions))
(_ (funcall completions-sort completions)))))
;; After sorting, group the candidates using the
@@ -2717,8 +2895,14 @@ Also respects the obsolete wrapper hook `completion-in-region-functions'.
completion-in-region-mode-predicate)
(setq-local minibuffer-completion-auto-choose nil)
(add-hook 'post-command-hook #'completion-in-region--postch)
- (push `(completion-in-region-mode . ,completion-in-region-mode-map)
- minor-mode-overriding-map-alist)))
+ (let* ((keymap completion-in-region-mode-map)
+ (keymap (if minibuffer-visible-completions
+ (make-composed-keymap
+ (list minibuffer-visible-completions-map
+ keymap))
+ keymap)))
+ (push `(completion-in-region-mode . ,keymap)
+ minor-mode-overriding-map-alist))))
;; Define-minor-mode added our keymap to minor-mode-map-alist, but we want it
;; on minor-mode-overriding-map-alist instead.
@@ -2960,8 +3144,59 @@ For customizing this mode, it is better to use
`minibuffer-setup-hook' and `minibuffer-exit-hook' rather than
the mode hook of this mode."
:syntax-table nil
- :interactive nil)
-
+ :interactive nil
+ ;; Enable text conversion, but always make sure `RET' does
+ ;; something.
+ (setq text-conversion-style 'action)
+ (when minibuffer-visible-completions
+ (setq-local minibuffer-completion-auto-choose nil)))
+
+(defcustom minibuffer-visible-completions nil
+ "Whether candidates shown in *Completions* can be navigated from minibuffer.
+When non-nil, if the *Completions* buffer is displayed in a window,
+you can use the arrow keys in the minibuffer to move the cursor in
+the window showing the *Completions* buffer. Typing `RET' selects
+the highlighted completion candidate.
+If the *Completions* buffer is not displayed on the screen, or this
+variable is nil, the arrow keys move point in the minibuffer as usual,
+and `RET' accepts the input typed into the minibuffer."
+ :type 'boolean
+ :version "30.1")
+
+(defvar minibuffer-visible-completions--always-bind nil
+ "If non-nil, force the `minibuffer-visible-completions' bindings on.")
+
+(defun minibuffer-visible-completions--filter (cmd)
+ "Return CMD if `minibuffer-visible-completions' bindings should be active."
+ (if minibuffer-visible-completions--always-bind
+ cmd
+ (when-let ((window (get-buffer-window "*Completions*" 0)))
+ (when (and (eq (buffer-local-value 'completion-reference-buffer
+ (window-buffer window))
+ (window-buffer (active-minibuffer-window)))
+ (if (eq cmd #'minibuffer-choose-completion-or-exit)
+ (with-current-buffer (window-buffer window)
+ (get-text-property (point) 'completion--string))
+ t))
+ cmd))))
+
+(defun minibuffer-visible-completions-bind (binding)
+ "Use BINDING when completions are visible.
+Return an item that is enabled only when a window
+displaying the *Completions* buffer exists."
+ `(menu-item
+ "" ,binding
+ :filter ,#'minibuffer-visible-completions--filter))
+
+(defvar-keymap minibuffer-visible-completions-map
+ :doc "Local keymap for minibuffer input with visible completions."
+ "<left>" (minibuffer-visible-completions-bind #'minibuffer-previous-completion)
+ "<right>" (minibuffer-visible-completions-bind #'minibuffer-next-completion)
+ "<up>" (minibuffer-visible-completions-bind #'minibuffer-previous-line-completion)
+ "<down>" (minibuffer-visible-completions-bind #'minibuffer-next-line-completion)
+ "RET" (minibuffer-visible-completions-bind #'minibuffer-choose-completion-or-exit)
+ "C-g" (minibuffer-visible-completions-bind #'minibuffer-hide-completions))
+
;;; Completion tables.
(defun minibuffer--double-dollars (str)
@@ -3490,8 +3725,13 @@ Like `internal-complete-buffer', but removes BUFFER from the completion list."
(defun completion-emacs22-try-completion (string table pred point)
(let ((suffix (substring string point))
(completion (try-completion (substring string 0 point) table pred)))
- (if (not (stringp completion))
- completion
+ (cond
+ ((eq completion t)
+ (if (equal "" suffix)
+ t
+ (cons string point)))
+ ((not (stringp completion)) completion)
+ (t
;; Merge a trailing / in completion with a / after point.
;; We used to only do it for word completion, but it seems to make
;; sense for all completions.
@@ -3505,7 +3745,7 @@ Like `internal-complete-buffer', but removes BUFFER from the completion list."
(eq ?/ (aref suffix 0)))
;; This leaves point after the / .
(setq suffix (substring suffix 1)))
- (cons (concat completion suffix) (length completion)))))
+ (cons (concat completion suffix) (length completion))))))
(defun completion-emacs22-all-completions (string table pred point)
(let ((beforepoint (substring string 0 point)))
@@ -3763,108 +4003,202 @@ one large \"hole\" and a clumped-together \"oo\" match) higher
than the latter (which has two \"holes\" and three
one-letter-long matches).")
+(defvar completion-lazy-hilit nil
+ "If non-nil, request lazy highlighting of completion candidates.
+
+Lisp programs (a.k.a. \"front ends\") that present completion
+candidates may opt to bind this variable to a non-nil value when
+calling functions (such as `completion-all-completions') which
+produce completion candidates. This tells the underlying
+completion styles that they do not need to fontify (i.e.,
+propertize with the `face' property) completion candidates in a
+way that highlights the matching parts. Then it is the front end
+which presents the candidates that becomes responsible for this
+fontification. The front end does that by calling the function
+`completion-lazy-hilit' on each completion candidate that is to be
+displayed to the user.
+
+Note that only some completion styles take advantage of this
+variable for optimization purposes. Other styles will ignore the
+hint and fontify eagerly as usual. It is still safe for a
+front end to call `completion-lazy-hilit' in these situations.
+
+To author a completion style that takes advantage of this variable,
+see `completion-lazy-hilit-fn' and `completion-pcm--hilit-commonality'.")
+
+(defvar completion-lazy-hilit-fn nil
+ "Fontification function set by lazy-highlighting completions styles.
+When a given style wants to enable support for `completion-lazy-hilit'
+\(which see), that style should set this variable to a function of one
+argument. It will be called with each completion candidate, a string, to
+be displayed to the user, and should destructively propertize these
+strings with the `face' property.")
+
+(defun completion-lazy-hilit (str)
+ "Return a copy of completion candidate STR that is `face'-propertized.
+See documentation of the variable `completion-lazy-hilit' for more
+details."
+ (if (and completion-lazy-hilit completion-lazy-hilit-fn)
+ (funcall completion-lazy-hilit-fn (copy-sequence str))
+ str))
+
+(defun completion--hilit-from-re (string regexp &optional point-idx)
+ "Fontify STRING using REGEXP POINT-IDX.
+`completions-common-part' and `completions-first-difference' are
+used. POINT-IDX is the position of point in the presumed \"PCM\"
+pattern that was used to generate derive REGEXP from."
+(let* ((md (and regexp (string-match regexp string) (cddr (match-data t))))
+ (pos (if point-idx (match-beginning point-idx) (match-end 0)))
+ (me (and md (match-end 0)))
+ (from 0))
+ (while md
+ (add-face-text-property from (pop md) 'completions-common-part nil string)
+ (setq from (pop md)))
+ (if (> (length string) pos)
+ (add-face-text-property
+ pos (1+ pos)
+ 'completions-first-difference
+ nil string))
+ (unless (or (not me) (= from me))
+ (add-face-text-property from me 'completions-common-part nil string))
+ string))
+
+(defun completion--flex-score-1 (md-groups match-end len)
+ "Compute matching score of completion.
+The score lies in the range between 0 and 1, where 1 corresponds to
+the full match.
+MD-GROUPS is the \"group\" part of the match data.
+MATCH-END is the end of the match.
+LEN is the length of the completion string."
+ (let* ((from 0)
+ ;; To understand how this works, consider these simple
+ ;; ascii diagrams showing how the pattern "foo"
+ ;; flex-matches "fabrobazo", "fbarbazoo" and
+ ;; "barfoobaz":
+
+ ;; f abr o baz o
+ ;; + --- + --- +
+
+ ;; f barbaz oo
+ ;; + ------ ++
+
+ ;; bar foo baz
+ ;; +++
+
+ ;; "+" indicates parts where the pattern matched. A
+ ;; "hole" in the middle of the string is indicated by
+ ;; "-". Note that there are no "holes" near the edges
+ ;; of the string. The completion score is a number
+ ;; bound by (0..1] (i.e., larger than (but not equal
+ ;; to) zero, and smaller or equal to one): the higher
+ ;; the better and only a perfect match (pattern equals
+ ;; string) will have score 1. The formula takes the
+ ;; form of a quotient. For the numerator, we use the
+ ;; number of +, i.e. the length of the pattern. For
+ ;; the denominator, it first computes
+ ;;
+ ;; hole_i_contrib = 1 + (Li-1)^(1/tightness)
+ ;;
+ ;; , for each hole "i" of length "Li", where tightness
+ ;; is given by `flex-score-match-tightness'. The
+ ;; final value for the denominator is then given by:
+ ;;
+ ;; (SUM_across_i(hole_i_contrib) + 1) * len
+ ;;
+ ;; , where "len" is the string's length.
+ (score-numerator 0)
+ (score-denominator 0)
+ (last-b 0))
+ (while (and md-groups (car md-groups))
+ (let ((a from)
+ (b (pop md-groups)))
+ (setq
+ score-numerator (+ score-numerator (- b a)))
+ (unless (or (= a last-b)
+ (zerop last-b)
+ (= a len))
+ (setq
+ score-denominator (+ score-denominator
+ 1
+ (expt (- a last-b 1)
+ (/ 1.0
+ flex-score-match-tightness)))))
+ (setq
+ last-b b))
+ (setq from (pop md-groups)))
+ ;; If `pattern' doesn't have an explicit trailing any, the
+ ;; regex `re' won't produce match data representing the
+ ;; region after the match. We need to account to account
+ ;; for that extra bit of match (bug#42149).
+ (unless (= from match-end)
+ (let ((a from)
+ (b match-end))
+ (setq
+ score-numerator (+ score-numerator (- b a)))
+ (unless (or (= a last-b)
+ (zerop last-b)
+ (= a len))
+ (setq
+ score-denominator (+ score-denominator
+ 1
+ (expt (- a last-b 1)
+ (/ 1.0
+ flex-score-match-tightness)))))
+ (setq
+ last-b b)))
+ (/ score-numerator (* len (1+ score-denominator)) 1.0)))
+
+(defvar completion--flex-score-last-md nil
+ "Helper variable for `completion--flex-score'.")
+
+(defun completion--flex-score (str re &optional dont-error)
+ "Compute flex score of completion STR based on RE.
+If DONT-ERROR, just return nil if RE doesn't match STR."
+ (let ((case-fold-search completion-ignore-case))
+ (cond ((string-match re str)
+ (let* ((match-end (match-end 0))
+ (md (cddr
+ (setq
+ completion--flex-score-last-md
+ (match-data t completion--flex-score-last-md)))))
+ (completion--flex-score-1 md match-end (length str))))
+ ((not dont-error)
+ (error "Internal error: %s does not match %s" re str)))))
+
+(defvar completion-pcm--regexp nil
+ "Regexp from PCM pattern in `completion-pcm--hilit-commonality'.")
+
(defun completion-pcm--hilit-commonality (pattern completions)
"Show where and how well PATTERN matches COMPLETIONS.
PATTERN, a list of symbols and strings as seen
`completion-pcm--merge-completions', is assumed to match every
-string in COMPLETIONS. Return a deep copy of COMPLETIONS where
-each string is propertized with `completion-score', a number
-between 0 and 1, and with faces `completions-common-part',
-`completions-first-difference' in the relevant segments."
+string in COMPLETIONS.
+
+If `completion-lazy-hilit' is nil, return a deep copy of
+COMPLETIONS where each string is propertized with
+`completion-score', a number between 0 and 1, and with faces
+`completions-common-part', `completions-first-difference' in the
+relevant segments.
+
+Else, if `completion-lazy-hilit' is t, return COMPLETIONS
+unchanged, but setup a suitable `completion-lazy-hilit-fn' (which
+see) for later lazy highlighting."
+ (setq completion-pcm--regexp nil
+ completion-lazy-hilit-fn nil)
(cond
((and completions (cl-loop for e in pattern thereis (stringp e)))
(let* ((re (completion-pcm--pattern->regex pattern 'group))
- (point-idx (completion-pcm--pattern-point-idx pattern))
- (case-fold-search completion-ignore-case)
- last-md)
- (mapcar
- (lambda (str)
- ;; Don't modify the string itself.
- (setq str (copy-sequence str))
- (unless (string-match re str)
- (error "Internal error: %s does not match %s" re str))
- (let* ((pos (if point-idx (match-beginning point-idx) (match-end 0)))
- (match-end (match-end 0))
- (md (cddr (setq last-md (match-data t last-md))))
- (from 0)
- (end (length str))
- ;; To understand how this works, consider these simple
- ;; ascii diagrams showing how the pattern "foo"
- ;; flex-matches "fabrobazo", "fbarbazoo" and
- ;; "barfoobaz":
-
- ;; f abr o baz o
- ;; + --- + --- +
-
- ;; f barbaz oo
- ;; + ------ ++
-
- ;; bar foo baz
- ;; +++
-
- ;; "+" indicates parts where the pattern matched. A
- ;; "hole" in the middle of the string is indicated by
- ;; "-". Note that there are no "holes" near the edges
- ;; of the string. The completion score is a number
- ;; bound by (0..1] (i.e., larger than (but not equal
- ;; to) zero, and smaller or equal to one): the higher
- ;; the better and only a perfect match (pattern equals
- ;; string) will have score 1. The formula takes the
- ;; form of a quotient. For the numerator, we use the
- ;; number of +, i.e. the length of the pattern. For
- ;; the denominator, it first computes
- ;;
- ;; hole_i_contrib = 1 + (Li-1)^(1/tightness)
- ;;
- ;; , for each hole "i" of length "Li", where tightness
- ;; is given by `flex-score-match-tightness'. The
- ;; final value for the denominator is then given by:
- ;;
- ;; (SUM_across_i(hole_i_contrib) + 1) * len
- ;;
- ;; , where "len" is the string's length.
- (score-numerator 0)
- (score-denominator 0)
- (last-b 0)
- (update-score-and-face
- (lambda (a b)
- "Update score and face given match range (A B)."
- (add-face-text-property a b
- 'completions-common-part
- nil str)
- (setq
- score-numerator (+ score-numerator (- b a)))
- (unless (or (= a last-b)
- (zerop last-b)
- (= a (length str)))
- (setq
- score-denominator (+ score-denominator
- 1
- (expt (- a last-b 1)
- (/ 1.0
- flex-score-match-tightness)))))
- (setq
- last-b b))))
- (while md
- (funcall update-score-and-face from (pop md))
- (setq from (pop md)))
- ;; If `pattern' doesn't have an explicit trailing any, the
- ;; regex `re' won't produce match data representing the
- ;; region after the match. We need to account to account
- ;; for that extra bit of match (bug#42149).
- (unless (= from match-end)
- (funcall update-score-and-face from match-end))
- (if (> (length str) pos)
- (add-face-text-property
- pos (1+ pos)
- 'completions-first-difference
- nil str))
- (unless (zerop (length str))
- (put-text-property
- 0 1 'completion-score
- (/ score-numerator (* end (1+ score-denominator)) 1.0) str)))
- str)
- completions)))
+ (point-idx (completion-pcm--pattern-point-idx pattern)))
+ (setq completion-pcm--regexp re)
+ (cond (completion-lazy-hilit
+ (setq completion-lazy-hilit-fn
+ (lambda (str) (completion--hilit-from-re str re point-idx)))
+ completions)
+ (t
+ (mapcar
+ (lambda (str)
+ (completion--hilit-from-re (copy-sequence str) re point-idx))
+ completions)))))
(t completions)))
(defun completion-pcm--find-all-completions (string table pred point
@@ -4039,7 +4373,9 @@ the same set of elements."
(unique (or (and (eq prefix t) (setq prefix fixed))
(and (stringp prefix)
(eq t (try-completion prefix comps))))))
- (unless (or (eq elem 'prefix)
+ ;; if the common prefix is unique, it also is a common
+ ;; suffix, so we should add it for `prefix' elements
+ (unless (or (and (eq elem 'prefix) (not unique))
(equal prefix ""))
(push prefix res))
;; If there's only one completion, `elem' is not useful
@@ -4199,36 +4535,39 @@ that is non-nil."
(defun completion--flex-adjust-metadata (metadata)
"If `flex' is actually doing filtering, adjust sorting."
- (let ((flex-is-filtering-p
- ;; JT@2019-12-23: FIXME: this is kinda wrong. What we need
- ;; to test here is "some input that actually leads/led to
- ;; flex filtering", not "something after the minibuffer
- ;; prompt". E.g. The latter is always true for file
- ;; searches, meaning we'll be doing extra work when we
- ;; needn't.
- (or (not (window-minibuffer-p))
- (> (point-max) (minibuffer-prompt-end))))
+ (let ((flex-is-filtering-p completion-pcm--regexp)
(existing-dsf
(completion-metadata-get metadata 'display-sort-function))
(existing-csf
(completion-metadata-get metadata 'cycle-sort-function)))
(cl-flet
- ((compose-flex-sort-fn
- (existing-sort-fn) ; wish `cl-flet' had proper indentation...
- (lambda (completions)
- (sort
- (funcall existing-sort-fn completions)
- (lambda (c1 c2)
- (let ((s1 (get-text-property 0 'completion-score c1))
- (s2 (get-text-property 0 'completion-score c2)))
- (> (or s1 0) (or s2 0))))))))
+ ((compose-flex-sort-fn (existing-sort-fn)
+ (lambda (completions)
+ (let* ((sorted (sort
+ (mapcar
+ (lambda (str)
+ (cons
+ (- (completion--flex-score
+ (or (get-text-property
+ 0 'completion--unquoted str)
+ str)
+ completion-pcm--regexp))
+ str))
+ (if existing-sort-fn
+ (funcall existing-sort-fn completions)
+ completions))
+ #'car-less-than-car))
+ (cell sorted))
+ ;; Reuse the list
+ (while cell
+ (setcar cell (cdar cell))
+ (pop cell))
+ sorted))))
`(metadata
,@(and flex-is-filtering-p
- `((display-sort-function
- . ,(compose-flex-sort-fn (or existing-dsf #'identity)))))
+ `((display-sort-function . ,(compose-flex-sort-fn existing-dsf))))
,@(and flex-is-filtering-p
- `((cycle-sort-function
- . ,(compose-flex-sort-fn (or existing-csf #'identity)))))
+ `((cycle-sort-function . ,(compose-flex-sort-fn existing-csf))))
,@(cdr metadata)))))
(defun completion-flex--make-flex-pattern (pattern)
@@ -4382,6 +4721,11 @@ See `completing-read' for the meaning of the arguments."
;; in minibuffer-local-filename-completion-map can
;; override bindings in base-keymap.
base-keymap)))
+ (keymap (if minibuffer-visible-completions
+ (make-composed-keymap
+ (list minibuffer-visible-completions-map
+ keymap))
+ keymap))
(buffer (current-buffer))
(c-i-c completion-ignore-case)
(result
@@ -4501,61 +4845,74 @@ selected by these commands to the minibuffer."
:type 'boolean
:version "29.1")
-(defun minibuffer-next-completion (&optional n)
+(defun minibuffer-next-completion (&optional n vertical)
"Move to the next item in its completions window from the minibuffer.
+When the optional argument VERTICAL is non-nil, move vertically
+to the next item on the next line using `next-line-completion'.
+Otherwise, move to the next item horizontally using `next-completion'.
When `minibuffer-completion-auto-choose' is non-nil, then also
-insert the selected completion to the minibuffer."
+insert the selected completion candidate to the minibuffer."
(interactive "p")
- (let ((auto-choose minibuffer-completion-auto-choose)
- (buf (current-buffer)))
+ (let ((auto-choose minibuffer-completion-auto-choose))
(with-minibuffer-completions-window
(when completions-highlight-face
(setq-local cursor-face-highlight-nonselected-window t))
- (next-completion (or n 1))
+ (if vertical
+ (next-line-completion (or n 1))
+ (next-completion (or n 1)))
(when auto-choose
- (let* ((completion-use-base-affixes t)
- ;; Backported fix for bug#62700
- (md
- (with-current-buffer buf
- (completion--field-metadata (minibuffer--completion-prompt-end))))
- (base-suffix
- (if (eq (alist-get 'category (cdr md)) 'file)
- (with-current-buffer buf
- (buffer-substring
- (save-excursion (or (search-forward "/" nil t) (point-max)))
- (point-max)))
- ""))
- (completion-base-affixes (list (car completion-base-affixes) base-suffix)))
+ (let ((completion-use-base-affixes t)
+ (completion-auto-deselect nil))
(choose-completion nil t t))))))
(defun minibuffer-previous-completion (&optional n)
"Move to the previous item in its completions window from the minibuffer.
When `minibuffer-completion-auto-choose' is non-nil, then also
-insert the selected completion to the minibuffer."
+insert the selected completion candidate to the minibuffer."
(interactive "p")
(minibuffer-next-completion (- (or n 1))))
+(defun minibuffer-next-line-completion (&optional n)
+ "Move to the next completion line from the minibuffer.
+This means to move to the completion candidate on the next line
+in the *Completions* buffer while point stays in the minibuffer.
+When `minibuffer-completion-auto-choose' is non-nil, then also
+insert the selected completion candidate to the minibuffer."
+ (interactive "p")
+ (minibuffer-next-completion (or n 1) t))
+
+(defun minibuffer-previous-line-completion (&optional n)
+ "Move to the previous completion line from the minibuffer.
+This means to move to the completion candidate on the previous line
+in the *Completions* buffer while point stays in the minibuffer.
+When `minibuffer-completion-auto-choose' is non-nil, then also
+insert the selected completion candidate to the minibuffer."
+ (interactive "p")
+ (minibuffer-next-completion (- (or n 1)) t))
+
(defun minibuffer-choose-completion (&optional no-exit no-quit)
"Run `choose-completion' from the minibuffer in its completions window.
-With prefix argument NO-EXIT, insert the completion at point to the
-minibuffer, but don't exit the minibuffer. When the prefix argument
+With prefix argument NO-EXIT, insert the completion candidate at point to
+the minibuffer, but don't exit the minibuffer. When the prefix argument
is not provided, then whether to exit the minibuffer depends on the value
of `completion-no-auto-exit'.
-If NO-QUIT is non-nil, insert the completion at point to the
+If NO-QUIT is non-nil, insert the completion candidate at point to the
minibuffer, but don't quit the completions window."
(interactive "P")
- ;; Backported fix for bug#62700
- (let* ((md (completion--field-metadata (minibuffer--completion-prompt-end)))
- (base-suffix
- (if (eq (alist-get 'category (cdr md)) 'file)
- (buffer-substring
- (save-excursion (or (search-forward "/" nil t) (point-max)))
- (point-max))
- "")))
- (with-minibuffer-completions-window
- (let ((completion-use-base-affixes t)
- (completion-base-affixes (list (car completion-base-affixes) base-suffix)))
- (choose-completion nil no-exit no-quit)))))
+ (with-minibuffer-completions-window
+ (let ((completion-use-base-affixes t))
+ (choose-completion nil no-exit no-quit))))
+
+(defun minibuffer-choose-completion-or-exit (&optional no-exit no-quit)
+ "Choose the completion from the minibuffer or exit the minibuffer.
+When `minibuffer-choose-completion' can't find a completion candidate
+in the completions window, then exit the minibuffer using its present
+contents."
+ (interactive "P")
+ (condition-case nil
+ (let ((choose-completion-deselect-if-after t))
+ (minibuffer-choose-completion no-exit no-quit))
+ (error (minibuffer-complete-and-exit))))
(defun minibuffer-complete-history ()
"Complete the minibuffer history as far as possible.
@@ -4572,13 +4929,15 @@ instead of the default completion table."
history)
(user-error "No history available"))))
;; FIXME: Can we make it work for CRM?
- (completion-in-region
- (minibuffer--completion-prompt-end) (point-max)
- (lambda (string pred action)
- (if (eq action 'metadata)
- '(metadata (display-sort-function . identity)
- (cycle-sort-function . identity))
- (complete-with-action action completions string pred))))))
+ (let ((completion-in-region-mode-predicate
+ (lambda () (get-buffer-window "*Completions*" 0))))
+ (completion-in-region
+ (minibuffer--completion-prompt-end) (point-max)
+ (lambda (string pred action)
+ (if (eq action 'metadata)
+ '(metadata (display-sort-function . identity)
+ (cycle-sort-function . identity))
+ (complete-with-action action completions string pred)))))))
(defun minibuffer-complete-defaults ()
"Complete minibuffer defaults as far as possible.
@@ -4589,7 +4948,9 @@ instead of the completion table."
(functionp minibuffer-default-add-function))
(setq minibuffer-default-add-done t
minibuffer-default (funcall minibuffer-default-add-function)))
- (let ((completions (ensure-list minibuffer-default)))
+ (let ((completions (ensure-list minibuffer-default))
+ (completion-in-region-mode-predicate
+ (lambda () (get-buffer-window "*Completions*" 0))))
(completion-in-region
(minibuffer--completion-prompt-end) (point-max)
(lambda (string pred action)
@@ -4644,6 +5005,232 @@ is included in the return value."
default)))
": "))
+
+;;; On screen keyboard support.
+;; Try to display the on screen keyboard whenever entering the
+;; mini-buffer, and hide it whenever leaving.
+
+(defvar minibuffer-on-screen-keyboard-timer nil
+ "Timer run upon exiting the minibuffer.
+It will hide the on screen keyboard when necessary.")
+
+(defvar minibuffer-on-screen-keyboard-displayed nil
+ "Whether or not the on-screen keyboard has been displayed.
+Set inside `minibuffer-setup-on-screen-keyboard'.")
+
+(defun minibuffer-setup-on-screen-keyboard ()
+ "Maybe display the on-screen keyboard in the current frame.
+Display the on-screen keyboard in the current frame if the
+last device to have sent an input event is not a keyboard.
+This is run upon minibuffer setup."
+ ;; Don't hide the on screen keyboard later on.
+ (when minibuffer-on-screen-keyboard-timer
+ (cancel-timer minibuffer-on-screen-keyboard-timer)
+ (setq minibuffer-on-screen-keyboard-timer nil))
+ (setq minibuffer-on-screen-keyboard-displayed nil)
+ (when (and (framep last-event-frame)
+ (not (memq (device-class last-event-frame
+ last-event-device)
+ '(keyboard core-keyboard))))
+ (setq minibuffer-on-screen-keyboard-displayed
+ (frame-toggle-on-screen-keyboard (selected-frame) nil))))
+
+(defun minibuffer-exit-on-screen-keyboard ()
+ "Hide the on-screen keyboard if it was displayed.
+Hide the on-screen keyboard in a timer set to run in 0.1 seconds.
+It will be canceled if the minibuffer is displayed again within
+that timeframe.
+
+Do not hide the on screen keyboard inside a recursive edit.
+Likewise, do not hide the on screen keyboard if point in the
+window that will be selected after exiting the minibuffer is not
+on read-only text.
+
+The latter is implemented in `touch-screen.el'."
+ (unless (or (not minibuffer-on-screen-keyboard-displayed)
+ (> (recursion-depth) 1))
+ (when minibuffer-on-screen-keyboard-timer
+ (cancel-timer minibuffer-on-screen-keyboard-timer))
+ (setq minibuffer-on-screen-keyboard-timer
+ (run-with-timer 0.1 nil #'frame-toggle-on-screen-keyboard
+ (selected-frame) t))))
+
+(add-hook 'minibuffer-setup-hook #'minibuffer-setup-on-screen-keyboard)
+(add-hook 'minibuffer-exit-hook #'minibuffer-exit-on-screen-keyboard)
+
+(defvar minibuffer-regexp-mode)
+
+(defun minibuffer--regexp-propertize ()
+ "In current minibuffer propertize parens and slashes in regexps.
+Put punctuation `syntax-table' property on selected paren and
+backslash characters in current buffer to make `show-paren-mode'
+and `blink-matching-paren' more user-friendly."
+ (let (in-char-alt-p)
+ (save-excursion
+ (with-silent-modifications
+ (remove-text-properties (point-min) (point-max) '(syntax-table nil))
+ (goto-char (point-min))
+ (while (re-search-forward
+ (rx (| (group "\\\\")
+ (: "\\" (| (group (in "(){}"))
+ (group "[")
+ (group "]")))
+ (group "[:" (+ (in "A-Za-z")) ":]")
+ (group "[")
+ (group "]")
+ (group (in "(){}"))))
+ (point-max) 'noerror)
+ (cond
+ ((match-beginning 1)) ; \\, skip
+ ((match-beginning 2) ; \( \) \{ \}
+ (if in-char-alt-p
+ ;; Within character alternative, set symbol syntax for
+ ;; paren only.
+ (put-text-property (1- (point)) (point) 'syntax-table '(3))
+ ;; Not within character alternative, set symbol syntax for
+ ;; backslash only.
+ (put-text-property (- (point) 2) (1- (point)) 'syntax-table '(3))))
+ ((match-beginning 3) ; \[
+ (if in-char-alt-p
+ (progn
+ ;; Set symbol syntax for backslash.
+ (put-text-property (- (point) 2) (1- (point)) 'syntax-table '(3))
+ ;; Re-read bracket we might be before a character class.
+ (backward-char))
+ ;; Set symbol syntax for bracket.
+ (put-text-property (1- (point)) (point) 'syntax-table '(3))))
+ ((match-beginning 4) ; \]
+ (if in-char-alt-p
+ (progn
+ ;; Within character alternative, set symbol syntax for
+ ;; backslash, exit alternative.
+ (put-text-property (- (point) 2) (1- (point)) 'syntax-table '(3))
+ (setq in-char-alt-p nil))
+ ;; Not within character alternative, set symbol syntax for
+ ;; bracket.
+ (put-text-property (1- (point)) (point) 'syntax-table '(3))))
+ ((match-beginning 5)) ; POSIX character class, skip
+ ((match-beginning 6) ; [
+ (if in-char-alt-p
+ ;; Within character alternative, set symbol syntax.
+ (put-text-property (1- (point)) (point) 'syntax-table '(3))
+ ;; Start new character alternative.
+ (setq in-char-alt-p t)
+ ;; Looking for immediately following non-closing ].
+ (when (looking-at "\\^?\\]")
+ ;; Non-special right bracket, set symbol syntax.
+ (goto-char (match-end 0))
+ (put-text-property (1- (point)) (point) 'syntax-table '(3)))))
+ ((match-beginning 7) ; ]
+ (if in-char-alt-p
+ (setq in-char-alt-p nil)
+ ;; The only warning we can emit before RET.
+ (message "Not in character alternative")))
+ ((match-beginning 8) ; (){}
+ ;; Plain parenthesis or brace, set symbol syntax.
+ (put-text-property (1- (point)) (point) 'syntax-table '(3)))))))))
+
+;; The following variable is set by 'minibuffer--regexp-before-change'.
+;; If non-nil, either 'minibuffer--regexp-post-self-insert' or
+;; 'minibuffer--regexp-after-change', whichever comes next, will
+;; propertize the minibuffer via 'minibuffer--regexp-propertize' and
+;; reset this variable to nil, avoiding to propertize the buffer twice.
+(defvar-local minibuffer--regexp-primed nil
+ "Non-nil when minibuffer contents change.")
+
+(defun minibuffer--regexp-before-change (_a _b)
+ "`minibuffer-regexp-mode' function on `before-change-functions'."
+ (setq minibuffer--regexp-primed t))
+
+(defun minibuffer--regexp-after-change (_a _b _c)
+ "`minibuffer-regexp-mode' function on `after-change-functions'."
+ (when minibuffer--regexp-primed
+ (setq minibuffer--regexp-primed nil)
+ (minibuffer--regexp-propertize)))
+
+(defun minibuffer--regexp-post-self-insert ()
+ "`minibuffer-regexp-mode' function on `post-self-insert-hook'."
+ (when minibuffer--regexp-primed
+ (setq minibuffer--regexp-primed nil)
+ (minibuffer--regexp-propertize)))
+
+(defvar minibuffer--regexp-prompt-regexp
+ "\\(?:Posix search\\|RE search\\|Search for regexp\\|Query replace regexp\\)"
+ "Regular expression compiled from `minibuffer-regexp-prompts'.")
+
+(defcustom minibuffer-regexp-prompts
+ '("Posix search" "RE search" "Search for regexp" "Query replace regexp")
+ "List of regular expressions that trigger `minibuffer-regexp-mode' features.
+The features of `minibuffer-regexp-mode' will be activated in a minibuffer
+interaction if and only if a prompt matching some regexp in this list
+appears at the beginning of the minibuffer.
+
+Setting this variable directly with `setq' has no effect; instead,
+either use \\[customize-option] interactively or use `setopt'."
+ :type '(repeat (string :tag "Prompt"))
+ :set (lambda (sym val)
+ (set-default sym val)
+ (when val
+ (setq minibuffer--regexp-prompt-regexp
+ (concat "\\(?:" (mapconcat 'regexp-quote val "\\|") "\\)"))))
+ :version "30.1")
+
+(defun minibuffer--regexp-setup ()
+ "Function to activate`minibuffer-regexp-mode' in current buffer.
+Run by `minibuffer-setup-hook'."
+ (if (and minibuffer-regexp-mode
+ (save-excursion
+ (goto-char (point-min))
+ (looking-at minibuffer--regexp-prompt-regexp)))
+ (progn
+ (setq-local parse-sexp-lookup-properties t)
+ (add-hook 'before-change-functions #'minibuffer--regexp-before-change nil t)
+ (add-hook 'after-change-functions #'minibuffer--regexp-after-change nil t)
+ (add-hook 'post-self-insert-hook #'minibuffer--regexp-post-self-insert nil t))
+ ;; Make sure.
+ (minibuffer--regexp-exit)))
+
+(defun minibuffer--regexp-exit ()
+ "Function to deactivate `minibuffer-regexp-mode' in current buffer.
+Run by `minibuffer-exit-hook'."
+ (with-silent-modifications
+ (remove-text-properties (point-min) (point-max) '(syntax-table nil)))
+ (setq-local parse-sexp-lookup-properties nil)
+ (remove-hook 'before-change-functions #'minibuffer--regexp-before-change t)
+ (remove-hook 'after-change-functions #'minibuffer--regexp-after-change t)
+ (remove-hook 'post-self-insert-hook #'minibuffer--regexp-post-self-insert t))
+
+(define-minor-mode minibuffer-regexp-mode
+ "Minor mode for editing regular expressions in the minibuffer.
+Highlight parens via `show-paren-mode' and `blink-matching-paren'
+in a user-friendly way, avoid reporting alleged paren mismatches
+and make sexp navigation more intuitive.
+
+The list of prompts activating this mode in specific minibuffer
+interactions is customizable via `minibuffer-regexp-prompts'."
+ :global t
+ :initialize 'custom-initialize-delay
+ :init-value t
+ (if minibuffer-regexp-mode
+ (progn
+ (add-hook 'minibuffer-setup-hook #'minibuffer--regexp-setup)
+ (add-hook 'minibuffer-exit-hook #'minibuffer--regexp-exit))
+ ;; Clean up - why is Vminibuffer_list not available in Lisp?
+ (dolist (buffer (buffer-list))
+ (when (and (minibufferp)
+ parse-sexp-lookup-properties
+ (with-current-buffer buffer
+ (save-excursion
+ (goto-char (point-min))
+ (looking-at minibuffer--regexp-prompt-regexp))))
+ (with-current-buffer buffer
+ (with-silent-modifications
+ (remove-text-properties
+ (point-min) (point-max) '(syntax-table nil)))
+ (setq-local parse-sexp-lookup-properties t))))
+ (remove-hook 'minibuffer-setup-hook #'minibuffer--regexp-setup)
+ (remove-hook 'minibuffer-exit-hook #'minibuffer--regexp-exit)))
+
(provide 'minibuffer)
;;; minibuffer.el ends here