summaryrefslogtreecommitdiff
path: root/lisp/isearch.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/isearch.el')
-rw-r--r--lisp/isearch.el1084
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.