]> git.eshelyaron.com Git - emacs.git/commitdiff
New minor mode 'minibuffer-regexp-mode'
authormartin rudalics <rudalics@gmx.at>
Wed, 6 Sep 2023 08:09:49 +0000 (10:09 +0200)
committerEli Zaretskii <eliz@gnu.org>
Thu, 7 Sep 2023 08:40:48 +0000 (11:40 +0300)
This mode is for editing regexps in minibuffer, it highlights
parens via `show-paren-mode' and `blink-matching-paren' in a
user-friendly way, avoids reporting false paren mismatches,
and makes sexp navigation more intuitive.
* lisp/minibuffer.el (minibuffer-regexp-mode)
(minibuffer--regexp-primed, minibuffer--regexp-prompt-regexp):
New variables.
(minibuffer--regexp-propertize, minibuffer--regexp-primed)
(minibuffer--regexp-before-change)
(minibuffer--regexp-after-change)
(minibuffer--regexp-post-self-insert, minibuffer--regexp-setup)
(minibuffer--regexp-exit, minibuffer-regexp-mode): New functions.
(minibuffer-regexp-prompts): New option.

* doc/lispref/minibuf.texi (Minibuffer Misc): Document the new
mode and its option.

* etc/NEWS: Announce the new mode.

Bug#50766

doc/lispref/minibuf.texi
etc/NEWS
lisp/minibuffer.el

index 8ff5c14055e22b487ddecc9fefaa8e97129bcbe8..0b877a25e68a0575a5e9586774fe445657a9fbf9 100644 (file)
@@ -2877,3 +2877,21 @@ This is the major mode used in inactive minibuffers.  It uses
 keymap @code{minibuffer-inactive-mode-map}.  This can be useful
 if the minibuffer is in a separate frame.  @xref{Minibuffers and Frames}.
 @end deffn
+
+@deffn Command minibuffer-regexp-mode
+This is a minor mode for editing regular expressions in the minibuffer.
+It highlight parens via @code{show-paren-mode} and
+@code{blink-matching-paren} in a user-friendly way, avoids reporting
+alleged paren mismatches and makes sexp navigation more intuitive.
+
+The list of prompts activating this mode in specific minibuffer
+interactions is customizable via @code{minibuffer-regexp-prompts}, see
+below.
+@end deffn
+
+@defopt minibuffer-regexp-prompts
+List of minibuffer prompts that trigger @code{minibuffer-regexp-mode}.
+@code{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.
+@end defopt
index 16d75802da592d04cb477da66739bb501b7b466b..db8f3760cbf5bac4b289d710b6998b2321dbc832 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -775,6 +775,13 @@ A major mode based on the tree-sitter library for editing HEEx files.
 A major mode based on the tree-sitter library for editing Elixir
 files.
 
++++
+** New global minor mode 'minibuffer-regexp-mode'.
+This is a minor mode for editing regular expressions in the minibuffer.
+It highlight parens via ‘show-paren-mode’ and ‘blink-matching-paren’ in
+a user-friendly way, avoids reporting alleged paren mismatches and makes
+sexp navigation more intuitive.
+
 ---
 ** The highly accessible Modus themes collection has eight items.
 The 'modus-operandi' and 'modus-vivendi' are the main themes that have
index 52a286018a1384804a814980e7d0eeb5d54137be..d43201eb36d29358aacf8504f8c42c3345a114df 100644 (file)
@@ -4663,6 +4663,170 @@ The latter is implemented in `touch-screen.el'."
 (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
+                "\\(\\\\\\\\\\)\\|\\(?:\\(?:\\\\\\)\\(?:\\([(){}]\\)\\|\\(\\[\\)\\|\\(\\]\\)\\)\\)\
+\\|\\(\\[:[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 "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