diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 1e1a6f852e..fed08c9018 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -4242,6 +4242,170 @@ format-prompt default))) ": ")) +(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 + "\\(\\\\\\\\\\)\\|\\(?:\\(?:\\\\\\)\\(?:\\([(){}]\\)\\|\\(\\[\\)\\|\\(\\]\\)\\)\\)\ +\\|\\(\\[:[a-zA-Z]+:\\]\\)\\|\\(\\[\\)\\|\\(\\]\\)\\|\\([(){}]\\)" + (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 minibuffer prompts that trigger `minibuffer-regexp-mode'. +`minibuffer-regexp-mode' is activated in a specific minibuffer +interaction if and only if a prompt in this list appears at the +beginning of the minibuffer." + :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 "29.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