diff options
Diffstat (limited to 'lisp/isearch.el')
-rw-r--r-- | lisp/isearch.el | 1084 |
1 files changed, 871 insertions, 213 deletions
diff --git a/lisp/isearch.el b/lisp/isearch.el index 25d6ad591eb..f150a3bba4b 100644 --- a/lisp/isearch.el +++ b/lisp/isearch.el @@ -54,6 +54,7 @@ ;;; Code: (eval-when-compile (require 'cl-lib)) +(declare-function tmm-menubar-keymap "tmm.el") ;; Some additional options and constants. @@ -67,8 +68,18 @@ (defcustom search-exit-option t - "Non-nil means random control characters terminate incremental search." - :type 'boolean) + "Defines what control characters do in incremental search. +If t, random control and meta characters terminate the search +and are then executed normally. +If `edit', edit the search string instead of exiting. +If `append', the characters which you type that are not interpreted by +the incremental search are simply appended to the search string. +If nil, run the command without exiting Isearch." + :type '(choice (const :tag "Terminate incremental search" t) + (const :tag "Edit the search string" edit) + (const :tag "Append control characters to the search string" append) + (const :tag "Don't terminate incremental search" nil)) + :version "27.1") (defcustom search-slow-window-lines 1 "Number of lines in slow search display windows. @@ -118,8 +129,10 @@ regexp incremental search. If the value is nil, or then each space you type matches literally, against one space. You might want to use something like \"[ \\t\\r\\n]+\" instead. -In the Customization buffer, that is `[' followed by a space, -a tab, a carriage return (control-M), a newline, and `]+'." +In the Customization buffer, that is `[' followed by a space, a +tab, a carriage return (control-M), a newline, and `]+'. Don't +add any capturing groups into this value; that can change the +numbering of existing capture groups in unexpected ways." :type '(choice (const :tag "Match Spaces Literally" nil) regexp) :version "24.3") @@ -287,9 +300,9 @@ are `word-search-regexp' \(`\\[isearch-toggle-word]'), `isearch-symbol-regexp' (defcustom isearch-lazy-highlight t "Controls the lazy-highlighting during incremental search. -When non-nil, all text in the buffer matching the current search -string is highlighted lazily (see `lazy-highlight-initial-delay' -and `lazy-highlight-interval'). +When non-nil, all text currently visible on the screen +matching the current search string is highlighted lazily +(see `lazy-highlight-initial-delay' and `lazy-highlight-interval'). When multiple windows display the current buffer, the highlighting is displayed only on the selected window, unless @@ -299,6 +312,16 @@ this variable is set to the symbol `all-windows'." :group 'lazy-highlight :group 'isearch) +(defcustom isearch-lazy-count nil + "Show match numbers in the search prompt. +When both this option and `isearch-lazy-highlight' are non-nil, +show the current match number and the total number of matches +in the buffer (or its restriction)." + :type 'boolean + :group 'lazy-count + :group 'isearch + :version "27.1") + ;;; Lazy highlight customization. (defgroup lazy-highlight nil @@ -308,10 +331,6 @@ this variable is set to the symbol `all-windows'." :group 'isearch :group 'matching) -(define-obsolete-variable-alias 'isearch-lazy-highlight-cleanup - 'lazy-highlight-cleanup - "22.1") - (defcustom lazy-highlight-cleanup t "Controls whether to remove extra highlighting after a search. If this is nil, extra highlighting can be \"manually\" removed with @@ -319,28 +338,16 @@ If this is nil, extra highlighting can be \"manually\" removed with :type 'boolean :group 'lazy-highlight) -(define-obsolete-variable-alias 'isearch-lazy-highlight-initial-delay - 'lazy-highlight-initial-delay - "22.1") - (defcustom lazy-highlight-initial-delay 0.25 "Seconds to wait before beginning to lazily highlight all matches." :type 'number :group 'lazy-highlight) -(define-obsolete-variable-alias 'isearch-lazy-highlight-interval - 'lazy-highlight-interval - "22.1") - (defcustom lazy-highlight-interval 0 ; 0.0625 "Seconds between lazily highlighting successive matches." :type 'number :group 'lazy-highlight) -(define-obsolete-variable-alias 'isearch-lazy-highlight-max-at-a-time - 'lazy-highlight-max-at-a-time - "22.1") - (defcustom lazy-highlight-max-at-a-time nil ; 20 (bug#25751) "Maximum matches to highlight at a time (for `lazy-highlight'). Larger values may reduce Isearch's responsiveness to user input; @@ -350,6 +357,27 @@ A value of nil means highlight all matches shown on the screen." (integer :tag "Some")) :group 'lazy-highlight) +(defcustom lazy-highlight-buffer-max-at-a-time 20 + "Maximum matches to highlight at a time (for `lazy-highlight-buffer'). +Larger values may reduce Isearch's responsiveness to user input; +smaller values make matches highlight slowly. +A value of nil means highlight all matches in the buffer." + :type '(choice (const :tag "All" nil) + (integer :tag "Some")) + :group 'lazy-highlight + :version "27.1") + +(defcustom lazy-highlight-buffer nil + "Controls the lazy-highlighting of the full buffer. +When non-nil, all text in the buffer matching the current search +string is highlighted lazily (see `lazy-highlight-initial-delay', +`lazy-highlight-interval' and `lazy-highlight-buffer-max-at-a-time'). +This is useful when `lazy-highlight-cleanup' is customized to nil +and doesn't remove full-buffer highlighting after a search." + :type 'boolean + :group 'lazy-highlight + :version "27.1") + (defface lazy-highlight '((((class color) (min-colors 88) (background light)) (:background "paleturquoise")) @@ -364,6 +392,29 @@ A value of nil means highlight all matches shown on the screen." :group 'lazy-highlight :group 'basic-faces) +;;; Lazy count customization. + +(defgroup lazy-count nil + "Lazy counting feature for reporting the number of matches." + :prefix "lazy-count-" + :version "27.1" + :group 'isearch + :group 'matching) + +(defcustom lazy-count-prefix-format "%s/%s " + "Format of the current/total number of matches for the prompt prefix." + :type '(choice (const :tag "No prefix" nil) + (string :tag "Prefix format string" "%s/%s ")) + :group 'lazy-count + :version "27.1") + +(defcustom lazy-count-suffix-format nil + "Format of the current/total number of matches for the prompt suffix." + :type '(choice (const :tag "No suffix" nil) + (string :tag "Suffix format string" " [%s of %s]")) + :group 'lazy-count + :version "27.1") + ;; Define isearch help map. @@ -434,6 +485,170 @@ This is like `describe-bindings', but displays only Isearch keys." ;; Define isearch-mode keymap. +(defun isearch-tmm-menubar () + "Run `tmm-menubar' while `isearch-mode' is enabled." + (interactive) + (require 'tmm) + (run-hooks 'menu-bar-update-hook) + (let ((command nil)) + (let ((menu-bar (tmm-menubar-keymap))) + (with-isearch-suspended + (setq command (let ((isearch-mode t)) ; Show bindings from + ; `isearch-mode-map' in + ; tmm's prompt. + (tmm-prompt menu-bar nil nil t))))) + (call-interactively command))) + +(defvar isearch-menu-bar-commands + '(isearch-tmm-menubar menu-bar-open mouse-minor-mode-menu) + "List of commands that can open a menu during Isearch.") + +(defvar isearch-menu-bar-yank-map + (let ((map (make-sparse-keymap))) + (define-key map [isearch-yank-pop] + '(menu-item "Previous kill" isearch-yank-pop + :help "Replace previous yanked kill on search string")) + (define-key map [isearch-yank-kill] + '(menu-item "Current kill" isearch-yank-kill + :help "Append current kill to search string")) + (define-key map [isearch-yank-line] + '(menu-item "Rest of line" isearch-yank-line + :help "Yank the rest of the current line on search string")) + (define-key map [isearch-yank-symbol-or-char] + '(menu-item "Symbol/char" + isearch-yank-symbol-or-char + :help "Yank next symbol or char on search string")) + (define-key map [isearch-yank-word-or-char] + '(menu-item "Word/char" + isearch-yank-word-or-char + :help "Yank next word or char on search string")) + (define-key map [isearch-yank-char] + '(menu-item "Char" isearch-yank-char + :help "Yank char at point on search string")) + map)) + +(defvar isearch-menu-bar-map + (let ((map (make-sparse-keymap "Isearch"))) + (define-key map [isearch-complete] + '(menu-item "Complete current search string" isearch-complete + :help "Complete current search string over search history")) + (define-key map [isearch-complete-separator] + '(menu-item "--")) + (define-key map [isearch-query-replace-regexp] + '(menu-item "Replace search string as regexp" isearch-query-replace-regexp + :help "Replace matches for current search string as regexp")) + (define-key map [isearch-query-replace] + '(menu-item "Replace search string" isearch-query-replace + :help "Replace matches for current search string")) + (define-key map [isearch-occur] + '(menu-item "Show all matches for search string" isearch-occur + :help "Show all matches for current search string")) + (define-key map [isearch-highlight-regexp] + '(menu-item "Highlight all matches for search string" + isearch-highlight-regexp + :help "Highlight all matches for current search string")) + (define-key map [isearch-search-replace-separator] + '(menu-item "--")) + (define-key map [isearch-toggle-specified-input-method] + '(menu-item "Turn on specific input method" + isearch-toggle-specified-input-method + :help "Turn on specific input method for search")) + (define-key map [isearch-toggle-input-method] + '(menu-item "Toggle input method" isearch-toggle-input-method + :help "Toggle input method for search")) + (define-key map [isearch-input-method-separator] + '(menu-item "--")) + (define-key map [isearch-char-by-name] + '(menu-item "Search for char by name" isearch-char-by-name + :help "Search for character by name")) + (define-key map [isearch-quote-char] + '(menu-item "Search for literal char" isearch-quote-char + :help "Search for literal char")) + (define-key map [isearch-special-char-separator] + '(menu-item "--")) + (define-key map [isearch-toggle-word] + '(menu-item "Word matching" isearch-toggle-word + :help "Word matching" + :button (:toggle + . (eq isearch-regexp-function 'word-search-regexp)))) + (define-key map [isearch-toggle-symbol] + '(menu-item "Symbol matching" isearch-toggle-symbol + :help "Symbol matching" + :button (:toggle + . (eq isearch-regexp-function + 'isearch-symbol-regexp)))) + (define-key map [isearch-toggle-regexp] + '(menu-item "Regexp matching" isearch-toggle-regexp + :help "Regexp matching" + :button (:toggle . isearch-regexp))) + (define-key map [isearch-toggle-invisible] + '(menu-item "Invisible text matching" isearch-toggle-invisible + :help "Invisible text matching" + :button (:toggle . isearch-invisible))) + (define-key map [isearch-toggle-char-fold] + '(menu-item "Character folding matching" isearch-toggle-char-fold + :help "Character folding matching" + :button (:toggle + . (eq isearch-regexp-function + 'char-fold-to-regexp)))) + (define-key map [isearch-toggle-case-fold] + '(menu-item "Case folding matching" isearch-toggle-case-fold + :help "Case folding matching" + :button (:toggle . isearch-case-fold-search))) + (define-key map [isearch-toggle-lax-whitespace] + '(menu-item "Lax whitespace matching" isearch-toggle-lax-whitespace + :help "Lax whitespace matching" + :button (:toggle . isearch-lax-whitespace))) + (define-key map [isearch-toggle-separator] + '(menu-item "--")) + (define-key map [isearch-yank-menu] + `(menu-item "Yank on search string" ,isearch-menu-bar-yank-map)) + (define-key map [isearch-edit-string] + '(menu-item "Edit current search string" isearch-edit-string + :help "Edit current search string")) + (define-key map [isearch-ring-retreat] + '(menu-item "Edit previous search string" isearch-ring-retreat + :help "Edit previous search string in Isearch history")) + (define-key map [isearch-ring-advance] + '(menu-item "Edit next search string" isearch-ring-advance + :help "Edit next search string in Isearch history")) + (define-key map [isearch-del-char] + '(menu-item "Delete last char from search string" isearch-del-char + :help "Delete last character from search string")) + (define-key map [isearch-delete-char] + '(menu-item "Undo last input item" isearch-delete-char + :help "Undo the effect of the last Isearch command")) + (define-key map [isearch-end-of-buffer] + '(menu-item "Go to last match" isearch-end-of-buffer + :help "Go to last occurrence of current search string")) + (define-key map [isearch-beginning-of-buffer] + '(menu-item "Go to first match" isearch-beginning-of-buffer + :help "Go to first occurrence of current search string")) + (define-key map [isearch-repeat-backward] + '(menu-item "Repeat search backward" isearch-repeat-backward + :help "Repeat current search backward")) + (define-key map [isearch-repeat-forward] + '(menu-item "Repeat search forward" isearch-repeat-forward + :help "Repeat current search forward")) + (define-key map [isearch-nonincremental] + '(menu-item "Nonincremental search" isearch-exit + :help "Start nonincremental search" + :visible (string-equal isearch-string ""))) + (define-key map [isearch-exit] + '(menu-item "Finish search" isearch-exit + :help "Finish search leaving point where it is" + :visible (not (string-equal isearch-string "")))) + (define-key map [isearch-abort] + '(menu-item "Remove characters not found" isearch-abort + :help "Quit current search" + :visible (not isearch-success))) + (define-key map [isearch-cancel] + `(menu-item "Cancel search" isearch-cancel + :help "Cancel current search and return to starting point" + :filter ,(lambda (binding) + (if isearch-success 'isearch-abort binding)))) + map)) + (defvar isearch-mode-map (let ((i 0) (map (make-keymap))) @@ -483,11 +698,15 @@ This is like `describe-bindings', but displays only Isearch keys." (define-key map [?\S-\ ] 'isearch-printing-char) (define-key map "\C-w" 'isearch-yank-word-or-char) - (define-key map "\M-\C-w" 'isearch-del-char) + (define-key map "\M-\C-w" 'isearch-yank-symbol-or-char) + (define-key map "\M-\C-d" 'isearch-del-char) (define-key map "\M-\C-y" 'isearch-yank-char) (define-key map "\C-y" 'isearch-yank-kill) (define-key map "\M-s\C-e" 'isearch-yank-line) + (define-key map "\M-s\M-<" 'isearch-beginning-of-buffer) + (define-key map "\M-s\M->" 'isearch-end-of-buffer) + (define-key map (char-to-string help-char) isearch-help-map) (define-key map [help] isearch-help-map) (define-key map [f1] isearch-help-map) @@ -523,6 +742,8 @@ This is like `describe-bindings', but displays only Isearch keys." (define-key map "\M-r" 'isearch-toggle-regexp) (define-key map "\M-e" 'isearch-edit-string) + (put 'isearch-toggle-case-fold :advertised-binding "\M-sc") + (put 'isearch-toggle-regexp :advertised-binding "\M-sr") (put 'isearch-edit-string :advertised-binding "\M-se") (define-key map "\M-se" 'isearch-edit-string) @@ -537,9 +758,59 @@ This is like `describe-bindings', but displays only Isearch keys." ;; characters to the search string. See iso-transl.el. (define-key map "\C-x8\r" 'isearch-char-by-name) + (define-key map [menu-bar search-menu] + (list 'menu-item "Isearch" isearch-menu-bar-map)) + (define-key map [remap tmm-menubar] 'isearch-tmm-menubar) + map) "Keymap for `isearch-mode'.") +(defvar isearch-tool-bar-old-map nil + "Variable holding the old local value of `tool-bar-map', if any.") + +(defun isearch-tool-bar-image (image-name) + "Return an image specification for IMAGE-NAME." + (eval (tool-bar--image-expression image-name))) + +(defvar isearch-tool-bar-map + (let ((map (make-sparse-keymap))) + (define-key map [isearch-describe-mode] + (list 'menu-item "Help" 'isearch-describe-mode + :help "Get help for Isearch" + :image '(isearch-tool-bar-image "help"))) + (define-key map [isearch-occur] + (list 'menu-item "Show hits" 'isearch-occur + :help "Show each search hit" + :image '(isearch-tool-bar-image "index"))) + (define-key map [isearch-query-replace] + (list 'menu-item "Replace" 'isearch-query-replace + :help "Replace search string" + :image '(isearch-tool-bar-image "search-replace"))) + (define-key map [isearch-delete-char] + (list 'menu-item "Undo" 'isearch-delete-char + :help "Undo last input item" + :image '(isearch-tool-bar-image "undo"))) + (define-key map [isearch-exit] + (list 'menu-item "Finish" 'isearch-exit + :help "Finish search leaving point where it is" + :image '(isearch-tool-bar-image "exit") + :visible '(not (string-equal isearch-string "")))) + (define-key map [isearch-cancel] + (list 'menu-item "Abort" 'isearch-cancel + :help "Abort search" + :image '(isearch-tool-bar-image "close") + :filter (lambda (binding) + (if isearch-success 'isearch-abort binding)))) + (define-key map [isearch-repeat-forward] + (list 'menu-item "Repeat forward" 'isearch-repeat-forward + :help "Repeat search forward" + :image '(isearch-tool-bar-image "right-arrow"))) + (define-key map [isearch-repeat-backward] + (list 'menu-item "Repeat backward" 'isearch-repeat-backward + :help "Repeat search backward" + :image '(isearch-tool-bar-image "left-arrow"))) + map)) + (defvar minibuffer-local-isearch-map (let ((map (make-sparse-keymap))) (set-keymap-parent map minibuffer-local-map) @@ -558,20 +829,28 @@ This is like `describe-bindings', but displays only Isearch keys." (defvar isearch-forward nil) ; Searching in the forward direction. (defvar isearch-regexp nil) ; Searching for a regexp. +;; We still support setting this to t for backwards compatibility. +(define-obsolete-variable-alias 'isearch-word + 'isearch-regexp-function "25.1") (defvar isearch-regexp-function nil "Regexp-based search mode for words/symbols. -If the value is a function (e.g. `isearch-symbol-regexp'), it is -called to convert a plain search string to a regexp used by -regexp search functions. +If non-nil, a function to convert a search string to a regexp +used by regexp search functions. + +The function should accept 1 or 2 arguments: the original string +to convert, and a flag, whose non-nil value means the match +doesn't have to start or end on a word boundary. The function +should return the corresponding regexp, a string. + The symbol property `isearch-message-prefix' put on this function specifies the prefix string displayed in the search message. +Existing functions you could use as values are `word-search-regexp', +`isearch-symbol-regexp', and `char-fold-to-regexp'. + This variable is set and changed during isearch. To change the default behavior used for searches, see `search-default-mode' instead.") -;; We still support setting this to t for backwards compatibility. -(define-obsolete-variable-alias 'isearch-word - 'isearch-regexp-function "25.1") (defvar isearch-lax-whitespace t "If non-nil, a space will match a sequence of whitespace chars. @@ -592,7 +871,7 @@ variable by the command `isearch-toggle-lax-whitespace'.") (defvar isearch-cmds nil "Stack of search status elements. Each element is an `isearch--state' struct where the slots are - [STRING MESSAGE POINT SUCCESS FORWARD OTHER-END WORD + [STRING MESSAGE POINT SUCCESS FORWARD OTHER-END WORD/REGEXP-FUNCTION ERROR WRAPPED BARRIER CASE-FOLD-SEARCH POP-FUN]") (defvar isearch-string "") ; The current search string. @@ -670,11 +949,19 @@ Each element is an `isearch--state' struct where the slots are ;; Minor-mode-alist changes - kind of redundant with the ;; echo area, but if isearching in multiple windows, it can be useful. +;; Also, clicking the mode-line indicator pops up +;; `isearch-menu-bar-map'. (or (assq 'isearch-mode minor-mode-alist) (nconc minor-mode-alist (list '(isearch-mode isearch-mode)))) +;; We add an entry for `isearch-mode' to `minor-mode-map-alist' so +;; that `isearch-menu-bar-map' can show on the menu bar. +(or (assq 'isearch-mode minor-mode-map-alist) + (nconc minor-mode-map-alist + (list (cons 'isearch-mode isearch-mode-map)))) + (defvar-local isearch-mode nil) ;; Name of the minor mode, if non-nil. (define-key global-map "\C-s" 'isearch-forward) @@ -700,6 +987,8 @@ Type \\[isearch-exit] to exit, leaving point at location found. Type LFD (C-j) to match end of line. Type \\[isearch-repeat-forward] to search again forward,\ \\[isearch-repeat-backward] to search again backward. +Type \\[isearch-beginning-of-buffer] to go to the first match,\ + \\[isearch-end-of-buffer] to go to the last match. Type \\[isearch-yank-word-or-char] to yank next word or character in buffer onto the end of the search string, and search for it. Type \\[isearch-del-char] to delete character from end of search string. @@ -829,21 +1118,26 @@ as a regexp. See the command `isearch-forward-regexp' for more information." (interactive "P\np") (isearch-mode nil (null not-regexp) nil (not no-recursive-edit))) -(defun isearch-forward-symbol-at-point () +(defun isearch-forward-symbol-at-point (&optional arg) "Do incremental search forward for a symbol found near point. Like ordinary incremental search except that the symbol found at point is added to the search string initially as a regexp surrounded by symbol boundary constructs \\_< and \\_>. -See the command `isearch-forward-symbol' for more information." - (interactive) +See the command `isearch-forward-symbol' for more information. +With a prefix argument, search for ARGth symbol forward if ARG is +positive, or search for ARGth symbol backward if ARG is negative." + (interactive "P") (isearch-forward-symbol nil 1) - (let ((bounds (find-tag-default-bounds))) + (let ((bounds (find-tag-default-bounds)) + (count (and arg (prefix-numeric-value arg)))) (cond (bounds (when (< (car bounds) (point)) (goto-char (car bounds))) (isearch-yank-string - (buffer-substring-no-properties (car bounds) (cdr bounds)))) + (buffer-substring-no-properties (car bounds) (cdr bounds))) + (when count + (isearch-repeat-forward count))) (t (setq isearch-error "No symbol at point") (isearch-push-state) @@ -915,11 +1209,18 @@ used to set the value of `isearch-regexp-function'." isearch-input-method-local-p (local-variable-p 'input-method-function) regexp-search-ring-yank-pointer nil + isearch-pre-scroll-point nil + isearch-pre-move-point nil + ;; Save the original value of `minibuffer-message-timeout', and ;; set it to nil so that isearch's messages don't get timed out. isearch-original-minibuffer-message-timeout minibuffer-message-timeout minibuffer-message-timeout nil) + (if (local-variable-p 'tool-bar-map) + (setq isearch-tool-bar-old-map tool-bar-map)) + (setq-local tool-bar-map isearch-tool-bar-map) + ;; We must bypass input method while reading key. When a user type ;; printable character, appropriate input method is turned on in ;; minibuffer to read multibyte characters. @@ -957,7 +1258,7 @@ used to set the value of `isearch-regexp-function'." (add-hook 'pre-command-hook 'isearch-pre-command-hook) (add-hook 'post-command-hook 'isearch-post-command-hook) - (add-hook 'mouse-leave-buffer-hook 'isearch-done) + (add-hook 'mouse-leave-buffer-hook 'isearch-mouse-leave-buffer) (add-hook 'kbd-macro-termination-hook 'isearch-done) ;; isearch-mode can be made modal (in the sense of not returning to @@ -1045,17 +1346,16 @@ For a failing search, NOPUSH is t. For going to the minibuffer to edit the search string, NOPUSH is t and EDIT is t." - (if isearch-resume-in-command-history - (let ((command `(isearch-resume ,isearch-string ,isearch-regexp - ,isearch-regexp-function ,isearch-forward - ,isearch-message - ',isearch-case-fold-search))) - (unless (equal (car command-history) command) - (setq command-history (cons command command-history))))) + (when isearch-resume-in-command-history + (add-to-history 'command-history + `(isearch-resume ,isearch-string ,isearch-regexp + ,isearch-regexp-function ,isearch-forward + ,isearch-message + ',isearch-case-fold-search))) (remove-hook 'pre-command-hook 'isearch-pre-command-hook) (remove-hook 'post-command-hook 'isearch-post-command-hook) - (remove-hook 'mouse-leave-buffer-hook 'isearch-done) + (remove-hook 'mouse-leave-buffer-hook 'isearch-mouse-leave-buffer) (remove-hook 'kbd-macro-termination-hook 'isearch-done) (setq isearch-lazy-highlight-start nil) (when (buffer-live-p isearch--current-buffer) @@ -1070,6 +1370,7 @@ NOPUSH is t and EDIT is t." (setq minibuffer-message-timeout isearch-original-minibuffer-message-timeout) (isearch-dehighlight) (lazy-highlight-cleanup lazy-highlight-cleanup) + (setq isearch-lazy-highlight-last-string nil) (let ((found-start (window-group-start)) (found-point (point))) (when isearch-window-configuration @@ -1087,6 +1388,12 @@ NOPUSH is t and EDIT is t." (setq input-method-function isearch-input-method-function) (kill-local-variable 'input-method-function)) + (if isearch-tool-bar-old-map + (progn + (setq-local tool-bar-map isearch-tool-bar-old-map) + (setq isearch-tool-bar-old-map nil)) + (kill-local-variable 'tool-bar-map)) + (force-mode-line-update) ;; If we ended in the middle of some intangible text, @@ -1119,22 +1426,45 @@ NOPUSH is t and EDIT is t." (and (not edit) isearch-recursive-edit (exit-recursive-edit))) +(defvar isearch-mouse-commands '(mouse-minor-mode-menu) + "List of mouse commands that are allowed during Isearch.") + +(defun isearch-mouse-leave-buffer () + "Exit Isearch unless the mouse command is allowed in Isearch. + +Mouse commands are allowed in Isearch if they have a non-nil +`isearch-scroll' property or if they are listed in +`isearch-mouse-commands'." + (unless (or (memq this-command isearch-mouse-commands) + (eq (get this-command 'isearch-scroll) t)) + (isearch-done))) + (defun isearch-update-ring (string &optional regexp) "Add STRING to the beginning of the search ring. REGEXP if non-nil says use the regexp search ring." - (add-to-history - (if regexp 'regexp-search-ring 'search-ring) - string - (if regexp regexp-search-ring-max search-ring-max))) - -;; Switching buffers should first terminate isearch-mode. -;; ;; For Emacs 19, the frame switch event is handled. -;; (defun isearch-switch-frame-handler () -;; (interactive) ;; Is this necessary? -;; ;; First terminate isearch-mode. -;; (isearch-done) -;; (isearch-clean-overlays) -;; (handle-switch-frame (car (cdr last-command-event)))) + (let ((history-delete-duplicates t)) + (add-to-history + (if regexp 'regexp-search-ring 'search-ring) + (isearch-string-propertize string) + (if regexp regexp-search-ring-max search-ring-max) + t))) + +(defun isearch-string-propertize (string &optional properties) + "Add isearch properties to the isearch string." + (unless properties + (setq properties `(isearch-case-fold-search ,isearch-case-fold-search)) + (unless isearch-regexp + (setq properties (append properties `(isearch-regexp-function ,isearch-regexp-function))))) + (apply 'propertize string properties)) + +(defun isearch-update-from-string-properties (string) + "Update isearch properties from the isearch string" + (when (plist-member (text-properties-at 0 string) 'isearch-case-fold-search) + (setq isearch-case-fold-search + (get-text-property 0 'isearch-case-fold-search string))) + (when (plist-member (text-properties-at 0 string) 'isearch-regexp-function) + (setq isearch-regexp-function + (get-text-property 0 'isearch-regexp-function string)))) ;; The search status structure and stack. @@ -1228,13 +1558,16 @@ If MSG is non-nil, use variable `isearch-message', otherwise `isearch-string'." (length succ-msg) 0)))) +(define-obsolete-variable-alias 'isearch-new-word + 'isearch-new-regexp-function "25.1") + (defvar isearch-new-regexp-function nil "Holds the next `isearch-regexp-function' inside `with-isearch-suspended'. If this is set inside code wrapped by the macro `with-isearch-suspended', then the value set will be used as the `isearch-regexp-function' once isearch resumes.") -(define-obsolete-variable-alias 'isearch-new-word - 'isearch-new-regexp-function "25.1") + +(defvar isearch-suspended nil) (defmacro with-isearch-suspended (&rest body) "Exit Isearch mode, run BODY, and reinvoke the pending search. @@ -1302,6 +1635,8 @@ You can update the global isearch variables by setting new values to isearch-original-minibuffer-message-timeout) old-point old-other-end) + (setq isearch-suspended t) + ;; Actually terminate isearching until editing is done. ;; This is so that the user can do anything without failure, ;; like switch buffers and start another isearch, and return. @@ -1316,6 +1651,8 @@ You can update the global isearch variables by setting new values to (unwind-protect (progn ,@body) + (setq isearch-suspended nil) + ;; Always resume isearching by restarting it. (isearch-mode isearch-forward isearch-regexp @@ -1334,6 +1671,8 @@ You can update the global isearch variables by setting new values to multi-isearch-file-list multi-isearch-file-list-new multi-isearch-buffer-list multi-isearch-buffer-list-new) + (isearch-update-from-string-properties isearch-string) + ;; Restore the minibuffer message before moving point. (funcall (or isearch-message-function #'isearch-message) nil t) @@ -1365,7 +1704,11 @@ You can update the global isearch variables by setting new values to ;; Reinvoke the pending search. (isearch-search) - (isearch-push-state) ; this pushes the correct state + ;; If no code has changed the search parameters, then pushing + ;; a new state of Isearch should not be necessary. + (unless (and isearch-cmds + (equal (car isearch-cmds) (isearch--get-state))) + (isearch-push-state)) ; this pushes the correct state (isearch-update) (if isearch-nonincremental (progn @@ -1377,6 +1720,7 @@ You can update the global isearch variables by setting new values to (message ""))))) (quit ; handle abort-recursive-edit + (setq isearch-suspended nil) (isearch-abort) ;; outside of let to restore outside global values ))) @@ -1399,7 +1743,9 @@ The following additional command keys are active while editing. (history-add-new-input nil) ;; Binding minibuffer-history-symbol to nil is a work-around ;; for some incompatibility with gmhist. - (minibuffer-history-symbol)) + (minibuffer-history-symbol) + ;; Search string might have meta information on text properties. + (minibuffer-allow-text-properties t)) (setq isearch-new-string (read-from-minibuffer (isearch-message-prefix nil isearch-nonincremental) @@ -1468,8 +1814,8 @@ Use `isearch-exit' to quit without signaling." (isearch-pop-state)) (isearch-update))) -(defun isearch-repeat (direction) - ;; Utility for isearch-repeat-forward and -backward. +(defun isearch-repeat (direction &optional count) + ;; Utility for isearch-repeat-forward and isearch-repeat-backward. (if (eq isearch-forward (eq direction 'forward)) ;; C-s in forward or C-r in reverse. (if (equal isearch-string "") @@ -1500,32 +1846,105 @@ Use `isearch-exit' to quit without signaling." (if (equal isearch-string "") (setq isearch-success t) - (if (and isearch-success - (equal (point) isearch-other-end) - (not isearch-just-started)) - ;; If repeating a search that found - ;; an empty string, ensure we advance. - (if (if isearch-forward (eobp) (bobp)) - ;; If there's nowhere to advance to, fail (and wrap next time). - (progn - (setq isearch-success nil) - (ding)) - (forward-char (if isearch-forward 1 -1)) + ;; For the case when count > 1, don't keep intermediate states + ;; added to isearch-cmds by isearch-push-state in this loop. + (let ((isearch-cmds isearch-cmds)) + (while (<= 0 (setq count (1- (or count 1)))) + (if (and isearch-success + (equal (point) isearch-other-end) + (not isearch-just-started)) + ;; If repeating a search that found + ;; an empty string, ensure we advance. + (if (if isearch-forward (eobp) (bobp)) + ;; If there's nowhere to advance to, fail (and wrap next time). + (progn + (setq isearch-success nil) + (ding)) + (forward-char (if isearch-forward 1 -1)) + (isearch-search)) (isearch-search)) - (isearch-search))) + (when (> count 0) + ;; Update isearch-cmds, so if isearch-search fails later, + ;; it can restore old successful state from isearch-cmds. + (isearch-push-state)) + ;; Stop looping on failure. + (when (or (not isearch-success) isearch-error) + (setq count 0))))) (isearch-push-state) (isearch-update)) -(defun isearch-repeat-forward () - "Repeat incremental search forwards." - (interactive) - (isearch-repeat 'forward)) - -(defun isearch-repeat-backward () - "Repeat incremental search backwards." - (interactive) - (isearch-repeat 'backward)) +(defun isearch-repeat-forward (&optional arg) + "Repeat incremental search forwards. +With a numeric argument, repeat the search ARG times. +A negative argument searches backwards. +\\<isearch-mode-map> +This command finds the next relative occurrence of the current +search string. To find the absolute occurrence from the beginning +of the buffer, type \\[isearch-beginning-of-buffer] with a numeric argument." + (interactive "P") + (if arg + (let ((count (prefix-numeric-value arg))) + (cond ((< count 0) + (isearch-repeat-backward (abs count)) + ;; Reverse the direction back + (isearch-repeat 'forward)) + (t + ;; Take into account one iteration to reverse direction + (when (not isearch-forward) (setq count (1+ count))) + (isearch-repeat 'forward count)))) + (isearch-repeat 'forward))) + +(defun isearch-repeat-backward (&optional arg) + "Repeat incremental search backwards. +With a numeric argument, repeat the search ARG times. +A negative argument searches forwards. +\\<isearch-mode-map> +This command finds the next relative occurrence of the current +search string. To find the absolute occurrence from the end +of the buffer, type \\[isearch-end-of-buffer] with a numeric argument." + (interactive "P") + (if arg + (let ((count (prefix-numeric-value arg))) + (cond ((< count 0) + (isearch-repeat-forward (abs count)) + ;; Reverse the direction back + (isearch-repeat 'backward)) + (t + ;; Take into account one iteration to reverse direction + (when isearch-forward (setq count (1+ count))) + (isearch-repeat 'backward count)))) + (isearch-repeat 'backward))) + +(defun isearch-beginning-of-buffer (&optional arg) + "Go to the first occurrence of the current search string. +Move point to the beginning of the buffer and search forwards from the top. +\\<isearch-mode-map> +With a numeric argument, go to the ARGth absolute occurrence counting from +the beginning of the buffer. To find the next relative occurrence forwards, +type \\[isearch-repeat-forward] with a numeric argument." + (interactive "p") + (if (and arg (< arg 0)) + (isearch-end-of-buffer (abs arg)) + ;; For the case when the match is at bobp, + ;; don't forward char in isearch-repeat + (setq isearch-just-started t) + (goto-char (point-min)) + (isearch-repeat 'forward arg))) + +(defun isearch-end-of-buffer (&optional arg) + "Go to the last occurrence of the current search string. +Move point to the end of the buffer and search backwards from the bottom. +\\<isearch-mode-map> +With a numeric argument, go to the ARGth absolute occurrence counting from +the end of the buffer. To find the next relative occurrence backwards, +type \\[isearch-repeat-backward] with a numeric argument." + (interactive "p") + (if (and arg (< arg 0)) + (isearch-beginning-of-buffer (abs arg)) + (setq isearch-just-started t) + (goto-char (point-max)) + (isearch-repeat 'backward arg))) ;;; Toggles for `isearch-regexp-function' and `search-default-mode'. @@ -1568,7 +1987,6 @@ Turning on word search turns off regexp mode.") Turning on symbol search turns off regexp mode.") (isearch-define-mode-toggle char-fold "'" char-fold-to-regexp "\ Turning on character-folding turns off regexp mode.") -(put 'char-fold-to-regexp 'isearch-message-prefix "char-fold ") (isearch-define-mode-toggle regexp "r" nil nil (setq isearch-regexp (not isearch-regexp)) @@ -1577,10 +1995,10 @@ Turning on character-folding turns off regexp mode.") (defun isearch--momentary-message (string) "Print STRING at the end of the isearch prompt for 1 second" (let ((message-log-max nil)) - (message "%s%s [%s]" + (message "%s%s%s" (isearch-message-prefix nil isearch-nonincremental) isearch-message - string)) + (propertize (format " [%s]" string) 'face 'minibuffer-prompt))) (sit-for 1)) (isearch-define-mode-toggle lax-whitespace " " nil @@ -1767,8 +2185,6 @@ the beginning or the end of the string need not match a symbol boundary." (if (string-match-p (format "%s\\'" not-word-symbol-re) string) not-word-symbol-re (unless lax "\\_>"))))))) -(put 'isearch-symbol-regexp 'isearch-message-prefix "symbol ") - ;; Search with lax whitespace (defun search-forward-lax-whitespace (string &optional bound noerror count) @@ -1827,7 +2243,9 @@ replacements from Isearch is `M-s w ... M-%'." ;; `exit-recursive-edit' in `isearch-done' that terminates ;; the execution of this command when it is non-nil. ;; We call `exit-recursive-edit' explicitly at the end below. - (isearch-recursive-edit nil)) + (isearch-recursive-edit nil) + (isearch-string-propertized + (isearch-string-propertize isearch-string))) (isearch-done nil t) (isearch-clean-overlays) (if (and isearch-other-end @@ -1840,20 +2258,20 @@ replacements from Isearch is `M-s w ... M-%'." (< (mark) (point)))))) (goto-char isearch-other-end)) (set query-replace-from-history-variable - (cons isearch-string + (cons isearch-string-propertized (symbol-value query-replace-from-history-variable))) (perform-replace - isearch-string + isearch-string-propertized (query-replace-read-to - isearch-string + isearch-string-propertized (concat "Query replace" (isearch--describe-regexp-mode (or delimited isearch-regexp-function) t) (if backward " backward" "") - (if (and transient-mark-mode mark-active) " in region" "")) + (if (use-region-p) " in region" "")) isearch-regexp) t isearch-regexp (or delimited isearch-regexp-function) nil nil - (if (and transient-mark-mode mark-active) (region-beginning)) - (if (and transient-mark-mode mark-active) (region-end)) + (if (use-region-p) (region-beginning)) + (if (use-region-p) (region-end)) backward)) (and isearch-recursive-edit (exit-recursive-edit))) @@ -1916,7 +2334,8 @@ characters in that string." 'isearch-regexp-function-descr (isearch--describe-regexp-mode isearch-regexp-function)) regexp) - nlines))) + nlines + (if (use-region-p) (region-bounds))))) (declare-function hi-lock-read-face-name "hi-lock" ()) @@ -2014,6 +2433,7 @@ If search string is empty, just beep." (defun isearch-yank-kill () "Pull string from kill ring into search string." (interactive) + (unless isearch-mode (isearch-mode t)) (isearch-yank-string (current-kill 0))) (defun isearch-yank-pop () @@ -2087,22 +2507,26 @@ If optional ARG is non-nil, pull in the next ARG characters." (interactive "p") (isearch-yank-internal (lambda () (forward-char arg) (point)))) -(declare-function subword-forward "subword" (&optional arg)) -(defun isearch-yank-word-or-char () - "Pull next character, subword or word from buffer into search string. -Subword is used when `subword-mode' is activated. " - (interactive) +(defun isearch--yank-char-or-syntax (syntax-list fn) (isearch-yank-internal (lambda () - (if (or (= (char-syntax (or (char-after) 0)) ?w) - (= (char-syntax (or (char-after (1+ (point))) 0)) ?w)) - (if (or (and (boundp 'subword-mode) subword-mode) - (and (boundp 'superword-mode) superword-mode)) - (subword-forward 1) - (forward-word 1)) + (if (or (memq (char-syntax (or (char-after) 0)) syntax-list) + (memq (char-syntax (or (char-after (1+ (point))) 0)) + syntax-list)) + (funcall fn 1) (forward-char 1)) (point)))) +(defun isearch-yank-word-or-char () + "Pull next character or word from buffer into search string." + (interactive) + (isearch--yank-char-or-syntax '(?w) 'forward-word)) + +(defun isearch-yank-symbol-or-char () + "Pull next character or symbol from buffer into search string." + (interactive) + (isearch--yank-char-or-syntax '(?w ?_) 'forward-symbol)) + (defun isearch-yank-word (&optional arg) "Pull next word from buffer into search string. If optional ARG is non-nil, pull in the next ARG words." @@ -2306,6 +2730,12 @@ to the barrier." (put 'split-window-right 'isearch-scroll t) (put 'split-window-below 'isearch-scroll t) (put 'enlarge-window 'isearch-scroll t) +(put 'enlarge-window-horizontally 'isearch-scroll t) +(put 'shrink-window-horizontally 'isearch-scroll t) +(put 'shrink-window 'isearch-scroll t) +;; The next two commands don't exit Isearch in isearch-mouse-leave-buffer +(put 'mouse-drag-mode-line 'isearch-scroll t) +(put 'mouse-drag-vertical-line 'isearch-scroll t) ;; Aliases for split-window-* (put 'split-window-vertically 'isearch-scroll t) @@ -2320,9 +2750,13 @@ to the barrier." (defcustom isearch-allow-scroll nil "Whether scrolling is allowed during incremental search. If non-nil, scrolling commands can be used in Isearch mode. -However, the current match will never scroll offscreen. -If nil, scrolling commands will first cancel Isearch mode." - :type 'boolean +However, you cannot scroll far enough that the current match is +no longer visible (is off screen). But if the value is `unlimited' +that limitation is removed and you can scroll any distance off screen. +If nil, scrolling commands exit Isearch mode." + :type '(choice (const :tag "Scrolling exits Isearch" nil) + (const :tag "Scrolling with current match on screen" t) + (const :tag "Scrolling with current match off screen" unlimited)) :group 'isearch) (defcustom isearch-allow-prefix t @@ -2384,6 +2818,22 @@ the bottom." (goto-char isearch-point)) (defvar isearch-pre-scroll-point nil) +(defvar isearch-pre-move-point nil) + +(defcustom isearch-yank-on-move nil + "Motion keys yank text to the search string while you move the cursor. +If `shift', extend the search string by motion commands while holding down +the shift key. The search string is extended by yanking text that +ends at the new position after moving point in the current buffer. +If t, extend the search string without the shift key pressed. +To enable motion commands, put the `isearch-move' property on their +symbols to `enabled', or to disable an automatically detected +shift-translated command, use the property value `disabled'." + :type '(choice (const :tag "Motion keys exit Isearch" nil) + (const :tag "Motion keys extend the search string" t) + (const :tag "Shifted motion keys extend the search string" shift)) + :group 'isearch + :version "27.1") (defun isearch-pre-command-hook () "Decide whether to exit Isearch mode before executing the command. @@ -2391,8 +2841,9 @@ Don't exit Isearch if the key sequence that invoked this command is bound in `isearch-mode-map', or if the invoked command is a prefix argument command (when `isearch-allow-prefix' is non-nil), or it is a scrolling command (when `isearch-allow-scroll' is non-nil). -Otherwise, exit Isearch (when `search-exit-option' is non-nil) -before the command is executed globally with terminated Isearch." +Otherwise, exit Isearch (when `search-exit-option' is t) +before the command is executed globally with terminated Isearch. +See more for options in `search-exit-option'." (let* ((key (this-single-command-keys)) (main-event (aref key 0))) (cond @@ -2400,7 +2851,12 @@ before the command is executed globally with terminated Isearch." ;; `set-transient-map' thingy like `universal-argument--mode'. ((not (eq overriding-terminal-local-map isearch--saved-overriding-local-map))) ;; Don't exit Isearch for isearch key bindings. - ((commandp (lookup-key isearch-mode-map key nil))) + ((or (commandp (lookup-key isearch-mode-map key nil)) + (commandp + (lookup-key + `(keymap (tool-bar menu-item nil ,isearch-tool-bar-map)) key)))) + ;; Allow key bindings that open a menubar. + ((memq this-command isearch-menu-bar-commands)) ;; Optionally edit the search string instead of exiting. ((eq search-exit-option 'edit) (setq this-command 'isearch-edit-string)) @@ -2413,29 +2869,63 @@ before the command is executed globally with terminated Isearch." (or (eq (get this-command 'isearch-scroll) t) (eq (get this-command 'scroll-command) t)))) (when isearch-allow-scroll - (setq isearch-pre-scroll-point (point)))) + (unless (eq isearch-allow-scroll 'unlimited) + (setq isearch-pre-scroll-point (point))))) ;; A mouse click on the isearch message starts editing the search string. ((and (eq (car-safe main-event) 'down-mouse-1) (window-minibuffer-p (posn-window (event-start main-event)))) ;; Swallow the up-event. (read-event) (setq this-command 'isearch-edit-string)) + ;; Don't terminate the search for motion commands. + ((and isearch-yank-on-move + (symbolp this-command) + (not (eq (get this-command 'isearch-move) 'disabled)) + (or (eq (get this-command 'isearch-move) 'enabled) + (and (eq isearch-yank-on-move t) + (stringp (nth 1 (interactive-form this-command))) + (string-match-p "^\\^" + (nth 1 (interactive-form this-command)))) + (and (eq isearch-yank-on-move 'shift) + this-command-keys-shift-translated))) + (setq this-command-keys-shift-translated nil) + (setq isearch-pre-move-point (point))) + ;; Append control characters to the search string + ((eq search-exit-option 'append) + (unless (memq nil (mapcar (lambda (k) (characterp k)) key)) + (isearch-process-search-string key key)) + (setq this-command 'ignore)) ;; Other characters terminate the search and are then executed normally. (search-exit-option (isearch-done) - (isearch-clean-overlays)) - ;; If search-exit-option is nil, run the command without exiting Isearch. - (t - (isearch-process-search-string key key))))) + (isearch-clean-overlays))))) (defun isearch-post-command-hook () - (when isearch-pre-scroll-point - (let ((ab-bel (isearch-string-out-of-window isearch-pre-scroll-point))) - (if ab-bel - (isearch-back-into-window (eq ab-bel 'above) isearch-pre-scroll-point) - (goto-char isearch-pre-scroll-point))) - (setq isearch-pre-scroll-point nil) - (isearch-update))) + (when isearch-pre-scroll-point + (let ((ab-bel (isearch-string-out-of-window isearch-pre-scroll-point))) + (if ab-bel + (isearch-back-into-window (eq ab-bel 'above) isearch-pre-scroll-point) + (goto-char isearch-pre-scroll-point))) + (setq isearch-pre-scroll-point nil) + (isearch-update)) + (when (eq isearch-allow-scroll 'unlimited) + (when isearch-lazy-highlight + (isearch-lazy-highlight-new-loop))) + (when isearch-pre-move-point + (when (not (eq isearch-pre-move-point (point))) + (let ((string (buffer-substring-no-properties + (or isearch-other-end isearch-opoint) (point)))) + (if isearch-regexp (setq string (regexp-quote string))) + (setq isearch-string string) + (setq isearch-message (mapconcat 'isearch-text-char-description + string "")) + (setq isearch-yank-flag t) + (setq isearch-forward (<= (or isearch-other-end isearch-opoint) (point))) + (when isearch-forward + (goto-char isearch-pre-move-point)) + (isearch-search-and-update))) + (setq isearch-pre-move-point nil)) + (force-mode-line-update)) (defun isearch-quote-char (&optional count) "Quote special characters for incremental search. @@ -2520,7 +3010,8 @@ Search is updated accordingly." length))) (setq isearch-string (nth yank-pointer ring) isearch-message (mapconcat 'isearch-text-char-description - isearch-string ""))))) + isearch-string "")) + (isearch-update-from-string-properties isearch-string)))) (defun isearch-ring-adjust (advance) ;; Helper for isearch-ring-advance and isearch-ring-retreat @@ -2634,12 +3125,16 @@ the word mode." (cond ;; 1. Do not use a description on the default search mode, ;; but only if the default search mode is non-nil. - ((or (and search-default-mode - (equal search-default-mode regexp-function)) - ;; Special case where `search-default-mode' is t - ;; (defaults to regexp searches). - (and (eq search-default-mode t) - (eq search-default-mode isearch-regexp))) "") + ((and (or (and search-default-mode + (equal search-default-mode regexp-function)) + ;; Special case where `search-default-mode' is t + ;; (defaults to regexp searches). + (and (eq search-default-mode t) + (eq search-default-mode isearch-regexp))) + ;; Also do not omit description in case of error + ;; in default non-literal search. + (or isearch-success (not (or regexp-function isearch-regexp)))) + "") ;; 2. Use the `isearch-message-prefix' set for ;; `regexp-function' if available. (regexp-function @@ -2682,6 +3177,8 @@ the word mode." (< (point) isearch-opoint))) "over") (if isearch-wrapped "wrapped ") + (if (and (not isearch-success) (not isearch-case-fold-search)) + "case-sensitive ") (let ((prefix "")) (advice-function-mapc (lambda (_ props) @@ -2705,15 +3202,41 @@ the word mode." (concat " [" current-input-method-title "]: ")) ": ") ))) - (propertize (concat (upcase (substring m 0 1)) (substring m 1)) + (propertize (concat (isearch-lazy-count-format) + (upcase (substring m 0 1)) (substring m 1)) 'face 'minibuffer-prompt))) (defun isearch-message-suffix (&optional c-q-hack) - (concat (if c-q-hack "^Q" "") - (if isearch-error - (concat " [" isearch-error "]") - "") - (or isearch-message-suffix-add ""))) + (propertize (concat (if c-q-hack "^Q" "") + (isearch-lazy-count-format 'suffix) + (if isearch-error + (concat " [" isearch-error "]") + "") + (or isearch-message-suffix-add "")) + 'face 'minibuffer-prompt)) + +(defun isearch-lazy-count-format (&optional suffix-p) + "Format the current match number and the total number of matches. +When SUFFIX-P is non-nil, the returned string is indended for +isearch-message-suffix prompt. Otherwise, for isearch-message-prefix." + (let ((format-string (if suffix-p + lazy-count-suffix-format + lazy-count-prefix-format))) + (if (and format-string + isearch-lazy-count + isearch-lazy-count-current + (not isearch-error) + (not isearch-suspended)) + (format format-string + (if isearch-forward + isearch-lazy-count-current + (if (eq isearch-lazy-count-current 0) + 0 + (- isearch-lazy-count-total + isearch-lazy-count-current + -1))) + (or isearch-lazy-count-total "?")) + ""))) ;; Searching @@ -2736,41 +3259,37 @@ Can be changed via `isearch-search-fun-function' for special needs." (defun isearch--lax-regexp-function-p () "Non-nil if next regexp-function call should be lax." - (not (or isearch-nonincremental - (null (car isearch-cmds)) - (eq (length isearch-string) - (length (isearch--state-string - (car isearch-cmds))))))) + (or (memq this-command '(isearch-printing-char isearch-del-char)) + isearch-yank-flag)) (defun isearch-search-fun-default () "Return default functions to use for the search." (lambda (string &optional bound noerror count) - ;; Use lax versions to not fail at the end of the word while - ;; the user adds and removes characters in the search string - ;; (or when using nonincremental word isearch) - (let ((search-spaces-regexp (when (cond - (isearch-regexp isearch-regexp-lax-whitespace) - (t isearch-lax-whitespace)) - search-whitespace-regexp))) - (condition-case er - (funcall - (if isearch-forward #'re-search-forward #'re-search-backward) + (let (;; Evaluate this before binding `search-spaces-regexp' which + ;; can break all sorts of regexp searches. In particular, + ;; calling `isearch-regexp-function' can trigger autoloading + ;; (Bug#35802). + (regexp (cond (isearch-regexp-function - (let ((lax (and (not bound) (isearch--lax-regexp-function-p)))) + (let ((lax (and (not bound) + (isearch--lax-regexp-function-p)))) (when lax (setq isearch-adjusted t)) (if (functionp isearch-regexp-function) (funcall isearch-regexp-function string lax) (word-search-regexp string lax)))) (isearch-regexp string) - (t (regexp-quote string))) - bound noerror count) - (search-failed - (signal (car er) - (let ((prefix (get isearch-regexp-function 'isearch-message-prefix))) - (if (and isearch-regexp-function (stringp prefix)) - (list (format "%s [using %ssearch]" string prefix)) - (cdr er))))))))) + (t (regexp-quote string)))) + ;; Use lax versions to not fail at the end of the word while + ;; the user adds and removes characters in the search string + ;; (or when using nonincremental word isearch) + (search-spaces-regexp (when (if isearch-regexp + isearch-regexp-lax-whitespace + isearch-lax-whitespace) + search-whitespace-regexp))) + (funcall + (if isearch-forward #'re-search-forward #'re-search-backward) + regexp bound noerror count)))) (defun isearch-search-string (string bound noerror) "Search for the first occurrence of STRING or its translation. @@ -2857,7 +3376,7 @@ Optional third argument, if t, means if fail just return nil (no error). (setq isearch-error (car (cdr lossage))) (cond ((string-match - "\\`Premature \\|\\`Unmatched \\|\\`Invalid " + "\\`Premature \\|\\`Unmatched " isearch-error) (setq isearch-error "incomplete input")) ((and (not isearch-regexp) @@ -2896,8 +3415,6 @@ Optional third argument, if t, means if fail just return nil (no error). (funcall (overlay-get ov 'isearch-open-invisible-temporary) ov nil) ;; Store the values for the `invisible' property, and then set it to nil. ;; This way the text hidden by this overlay becomes visible. - - ;; In 19.34 this does not exist so I cannot test it. (overlay-put ov 'isearch-invisible (overlay-get ov 'invisible)) (overlay-put ov 'invisible nil))) @@ -3127,15 +3644,23 @@ since they have special meaning in a regexp." (defvar isearch-lazy-highlight-window-group nil) (defvar isearch-lazy-highlight-window-start nil) (defvar isearch-lazy-highlight-window-end nil) +(defvar isearch-lazy-highlight-window-start-changed nil) +(defvar isearch-lazy-highlight-window-end-changed nil) +(defvar isearch-lazy-highlight-point-min nil) +(defvar isearch-lazy-highlight-point-max nil) +(defvar isearch-lazy-highlight-buffer nil) (defvar isearch-lazy-highlight-case-fold-search nil) (defvar isearch-lazy-highlight-regexp nil) (defvar isearch-lazy-highlight-lax-whitespace nil) (defvar isearch-lazy-highlight-regexp-lax-whitespace nil) -(defvar isearch-lazy-highlight-regexp-function nil) (define-obsolete-variable-alias 'isearch-lazy-highlight-word 'isearch-lazy-highlight-regexp-function "25.1") +(defvar isearch-lazy-highlight-regexp-function nil) (defvar isearch-lazy-highlight-forward nil) (defvar isearch-lazy-highlight-error nil) +(defvar isearch-lazy-count-current nil) +(defvar isearch-lazy-count-total nil) +(defvar isearch-lazy-count-hash (make-hash-table)) (defun lazy-highlight-cleanup (&optional force procrastinate) "Stop lazy highlighting and remove extra highlighting from current buffer. @@ -3153,10 +3678,6 @@ This function is called when exiting an incremental search if (cancel-timer isearch-lazy-highlight-timer) (setq isearch-lazy-highlight-timer nil))) -(define-obsolete-function-alias 'isearch-lazy-highlight-cleanup - 'lazy-highlight-cleanup - "22.1") - (defun isearch-lazy-highlight-new-loop (&optional beg end) "Cleanup any previous `lazy-highlight' loop and begin a new one. BEG and END specify the bounds within which highlighting should occur. @@ -3179,17 +3700,46 @@ by other Emacs features." isearch-lax-whitespace)) (not (eq isearch-lazy-highlight-regexp-lax-whitespace isearch-regexp-lax-whitespace)) - (not (= (window-group-start) - isearch-lazy-highlight-window-start)) - (not (= (window-group-end) ; Window may have been split/joined. - isearch-lazy-highlight-window-end)) (not (eq isearch-forward isearch-lazy-highlight-forward)) ;; In case we are recovering from an error. (not (equal isearch-error - isearch-lazy-highlight-error)))) + isearch-lazy-highlight-error)) + (if lazy-highlight-buffer + (not (= (point-min) + isearch-lazy-highlight-point-min)) + (setq isearch-lazy-highlight-window-start-changed + (not (= (window-group-start) + isearch-lazy-highlight-window-start)))) + (if lazy-highlight-buffer + (not (= (point-max) + isearch-lazy-highlight-point-max)) + (setq isearch-lazy-highlight-window-end-changed + (not (= (window-group-end) ; Window may have been split/joined. + isearch-lazy-highlight-window-end)))))) ;; something important did indeed change (lazy-highlight-cleanup t (not (equal isearch-string ""))) ;stop old timer + (when isearch-lazy-count + (when (or (equal isearch-string "") + ;; Check if this place was reached by a condition above + ;; other than changed window boundaries (that shouldn't + ;; reset the counter) + (and (not isearch-lazy-highlight-window-start-changed) + (not isearch-lazy-highlight-window-end-changed)) + ;; Also check for changes in buffer boundaries in + ;; a possibly narrowed buffer in case lazy-highlight-buffer + ;; is nil, thus the same check was not performed above + (not (= (point-min) + isearch-lazy-highlight-point-min)) + (not (= (point-max) + isearch-lazy-highlight-point-max))) + ;; Reset old counter before going to count new numbers + (clrhash isearch-lazy-count-hash) + (setq isearch-lazy-count-current nil + isearch-lazy-count-total nil) + (funcall (or isearch-message-function #'isearch-message)))) + (setq isearch-lazy-highlight-window-start-changed nil) + (setq isearch-lazy-highlight-window-end-changed nil) (setq isearch-lazy-highlight-error isearch-error) ;; It used to check for `(not isearch-error)' here, but actually ;; lazy-highlighting might find matches to highlight even when @@ -3200,6 +3750,9 @@ by other Emacs features." isearch-lazy-highlight-window-group (selected-window-group) isearch-lazy-highlight-window-start (window-group-start) isearch-lazy-highlight-window-end (window-group-end) + isearch-lazy-highlight-point-min (point-min) + isearch-lazy-highlight-point-max (point-max) + isearch-lazy-highlight-buffer lazy-highlight-buffer ;; Start lazy-highlighting at the beginning of the found ;; match (`isearch-other-end'). If no match, use point. ;; One of the next two variables (depending on search direction) @@ -3217,12 +3770,31 @@ by other Emacs features." isearch-lazy-highlight-regexp-lax-whitespace isearch-regexp-lax-whitespace isearch-lazy-highlight-regexp-function isearch-regexp-function isearch-lazy-highlight-forward isearch-forward) + ;; Extend start/end to match whole string at point (bug#19353) + (if isearch-lazy-highlight-forward + (setq isearch-lazy-highlight-start + (min (+ isearch-lazy-highlight-start + (1- (length isearch-lazy-highlight-last-string))) + (point-max))) + (setq isearch-lazy-highlight-end + (max (- isearch-lazy-highlight-end + (1- (length isearch-lazy-highlight-last-string))) + (point-min)))) (unless (equal isearch-string "") (setq isearch-lazy-highlight-timer (run-with-idle-timer lazy-highlight-initial-delay nil - 'isearch-lazy-highlight-start))))) - -(defun isearch-lazy-highlight-search () + 'isearch-lazy-highlight-start)))) + ;; Update the current match number only in isearch-mode and + ;; unless isearch-mode is used specially with isearch-message-function + (when (and isearch-lazy-count isearch-mode (null isearch-message-function)) + ;; Update isearch-lazy-count-current only when it was already set + ;; at the end of isearch-lazy-highlight-buffer-update + (when isearch-lazy-count-current + (setq isearch-lazy-count-current + (gethash (point) isearch-lazy-count-hash 0)) + (isearch-message)))) + +(defun isearch-lazy-highlight-search (string bound) "Search ahead for the next or previous match, for lazy highlighting. Attempt to do the search exactly the way the pending Isearch would." (condition-case nil @@ -3236,24 +3808,10 @@ Attempt to do the search exactly the way the pending Isearch would." (isearch-forward isearch-lazy-highlight-forward) (search-invisible nil) ; don't match invisible text (retry t) - (success nil) - (bound (if isearch-lazy-highlight-forward - (min (or isearch-lazy-highlight-end-limit (point-max)) - (if isearch-lazy-highlight-wrapped - (+ isearch-lazy-highlight-start - ;; Extend bound to match whole string at point - (1- (length isearch-lazy-highlight-last-string))) - (window-group-end))) - (max (or isearch-lazy-highlight-start-limit (point-min)) - (if isearch-lazy-highlight-wrapped - (- isearch-lazy-highlight-end - ;; Extend bound to match whole string at point - (1- (length isearch-lazy-highlight-last-string))) - (window-group-start)))))) + (success nil)) ;; Use a loop like in `isearch-search'. (while retry - (setq success (isearch-search-string - isearch-lazy-highlight-last-string bound t)) + (setq success (isearch-search-string string bound t)) ;; Clear RETRY unless the search predicate says ;; to skip this search hit. (if (or (not success) @@ -3265,6 +3823,17 @@ Attempt to do the search exactly the way the pending Isearch would." success) (error nil))) +(defun isearch-lazy-highlight-match (mb me) + (let ((ov (make-overlay mb me))) + (push ov isearch-lazy-highlight-overlays) + ;; 1000 is higher than ediff's 100+, + ;; but lower than isearch main overlay's 1001 + (overlay-put ov 'priority 1000) + (overlay-put ov 'face 'lazy-highlight) + (unless (or (eq isearch-lazy-highlight 'all-windows) + isearch-lazy-highlight-buffer) + (overlay-put ov 'window (selected-window))))) + (defun isearch-lazy-highlight-start () "Start a new lazy-highlight updating loop." (lazy-highlight-cleanup t) ;remove old overlays @@ -3274,19 +3843,32 @@ Attempt to do the search exactly the way the pending Isearch would." "Update highlighting of other matches for current search." (let ((max lazy-highlight-max-at-a-time) (looping t) - nomore) + nomore window-start window-end) (with-local-quit (save-selected-window (if (and (window-live-p isearch-lazy-highlight-window) (not (memq (selected-window) isearch-lazy-highlight-window-group))) (select-window isearch-lazy-highlight-window)) + (setq window-start (window-group-start)) + (setq window-end (window-group-end)) (save-excursion (save-match-data (goto-char (if isearch-lazy-highlight-forward isearch-lazy-highlight-end isearch-lazy-highlight-start)) (while looping - (let ((found (isearch-lazy-highlight-search))) + (let* ((bound (if isearch-lazy-highlight-forward + (min (or isearch-lazy-highlight-end-limit (point-max)) + (if isearch-lazy-highlight-wrapped + isearch-lazy-highlight-start + window-end)) + (max (or isearch-lazy-highlight-start-limit (point-min)) + (if isearch-lazy-highlight-wrapped + isearch-lazy-highlight-end + window-start)))) + (found (isearch-lazy-highlight-search + isearch-lazy-highlight-last-string + bound))) (when max (setq max (1- max)) (if (<= max 0) @@ -3298,24 +3880,17 @@ Attempt to do the search exactly the way the pending Isearch would." (if isearch-lazy-highlight-forward (if (= mb (if isearch-lazy-highlight-wrapped isearch-lazy-highlight-start - (window-group-end))) + window-end)) (setq found nil) (forward-char 1)) (if (= mb (if isearch-lazy-highlight-wrapped isearch-lazy-highlight-end - (window-group-start))) + window-start)) (setq found nil) (forward-char -1))) ;; non-zero-length match - (let ((ov (make-overlay mb me))) - (push ov isearch-lazy-highlight-overlays) - ;; 1000 is higher than ediff's 100+, - ;; but lower than isearch main overlay's 1001 - (overlay-put ov 'priority 1000) - (overlay-put ov 'face 'lazy-highlight) - (unless (eq isearch-lazy-highlight 'all-windows) - (overlay-put ov 'window (selected-window))))) + (isearch-lazy-highlight-match mb me)) ;; Remember the current position of point for ;; the next call of `isearch-lazy-highlight-update' ;; when `lazy-highlight-max-at-a-time' is too small. @@ -3331,17 +3906,100 @@ Attempt to do the search exactly the way the pending Isearch would." (setq isearch-lazy-highlight-wrapped t) (if isearch-lazy-highlight-forward (progn - (setq isearch-lazy-highlight-end (window-group-start)) + (setq isearch-lazy-highlight-end window-start) (goto-char (max (or isearch-lazy-highlight-start-limit (point-min)) - (window-group-start)))) - (setq isearch-lazy-highlight-start (window-group-end)) + window-start))) + (setq isearch-lazy-highlight-start window-end) (goto-char (min (or isearch-lazy-highlight-end-limit (point-max)) - (window-group-end)))))))) - (unless nomore + window-end))))))) + (if nomore + (when (or isearch-lazy-highlight-buffer + (and isearch-lazy-count (null isearch-lazy-count-current))) + (if isearch-lazy-highlight-forward + (setq isearch-lazy-highlight-end (point-min)) + (setq isearch-lazy-highlight-start (point-max))) + (run-at-time lazy-highlight-interval nil + 'isearch-lazy-highlight-buffer-update)) (setq isearch-lazy-highlight-timer (run-at-time lazy-highlight-interval nil 'isearch-lazy-highlight-update))))))))) +(defun isearch-lazy-highlight-buffer-update () + "Update highlighting of other matches in the full buffer." + (let ((max lazy-highlight-buffer-max-at-a-time) + (looping t) + nomore window-start window-end + (opoint (point))) + (with-local-quit + (save-selected-window + (if (and (window-live-p isearch-lazy-highlight-window) + (not (memq (selected-window) isearch-lazy-highlight-window-group))) + (select-window isearch-lazy-highlight-window)) + (setq window-start (window-group-start)) + (setq window-end (window-group-end)) + (save-excursion + (save-match-data + (goto-char (if isearch-lazy-highlight-forward + isearch-lazy-highlight-end + isearch-lazy-highlight-start)) + (while looping + (let* ((bound (if isearch-lazy-highlight-forward + (or isearch-lazy-highlight-end-limit (point-max)) + (or isearch-lazy-highlight-start-limit (point-min)))) + (found (isearch-lazy-highlight-search + isearch-lazy-highlight-last-string + bound))) + (when max + (setq max (1- max)) + (if (<= max 0) + (setq looping nil))) + (if found + (let ((mb (match-beginning 0)) + (me (match-end 0))) + (if (= mb me) ;zero-length match + (if isearch-lazy-highlight-forward + (if (= mb (point-max)) + (setq found nil) + (forward-char 1)) + (if (= mb (point-min)) + (setq found nil) + (forward-char -1))) + (when isearch-lazy-count + (setq isearch-lazy-count-total + (1+ (or isearch-lazy-count-total 0))) + (puthash (if isearch-lazy-highlight-forward me mb) + isearch-lazy-count-total + isearch-lazy-count-hash)) + ;; Don't highlight the match when this loop is used + ;; only to count matches or when matches were already + ;; highlighted within the current window boundaries + ;; by isearch-lazy-highlight-update + (unless (or (not isearch-lazy-highlight-buffer) + (and (>= mb window-start) (<= me window-end))) + ;; non-zero-length match + (isearch-lazy-highlight-match mb me))) + ;; Remember the current position of point for + ;; the next call of `isearch-lazy-highlight-update' + ;; when `lazy-highlight-buffer-max-at-a-time' is too small. + (if isearch-lazy-highlight-forward + (setq isearch-lazy-highlight-end (point)) + (setq isearch-lazy-highlight-start (point))))) + + ;; not found or zero-length match at the search bound + (if (not found) + (setq looping nil + nomore t)))) + (if nomore + (when (and isearch-lazy-count isearch-mode (null isearch-message-function)) + (unless isearch-lazy-count-total + (setq isearch-lazy-count-total 0)) + (setq isearch-lazy-count-current + (gethash opoint isearch-lazy-count-hash 0)) + (isearch-message)) + (setq isearch-lazy-highlight-timer + (run-at-time lazy-highlight-interval nil + 'isearch-lazy-highlight-buffer-update))))))))) + (defun isearch-resume (string regexp word forward message case-fold) "Resume an incremental search. STRING is the string or regexp searched for. |