"Function to save a function restoring the mode-specific Isearch state
to the search status stack.")
-(defvar isearch-filter-predicates nil
- "Predicates that filter the search hits that would normally be available.
-Search hits that dissatisfy the list of predicates are skipped.
-Each function in this list has two arguments: the positions of
-start and end of text matched by the search.
-The search loop uses `run-hook-with-args-until-failure' to call
-each predicate in order, and when one of the predicates returns nil,
-skips this match and continues searching for the next match.
-When the list of predicates is empty, `run-hook-with-args-until-failure'
-returns non-nil that means that the found match is accepted.
-The property `isearch-message-prefix' put on the predicate's symbol
-specifies the prefix string displyed in the search message.")
-(define-obsolete-variable-alias 'isearch-filter-predicate
- 'isearch-filter-predicates
- "24.4")
+(defvar isearch-filter-predicate #'isearch-filter-visible
+ "Predicate that filter the search hits that would normally be available.
+Search hits that dissatisfy the predicate are skipped. The function
+has two arguments: the positions of start and end of text matched by
+the search. If this function returns nil, continue searching without
+stopping at this match.
+If you use `add-function' to modify this variable, you can use the
+`isearch-message-prefix' advice property to specify the prefix string
+displayed in the search message.")
;; Search ring.
(define-key esc-map "\C-r" 'isearch-backward-regexp)
(define-key search-map "w" 'isearch-forward-word)
(define-key search-map "_" 'isearch-forward-symbol)
+(define-key search-map "." 'isearch-forward-symbol-at-point)
;; Entry points to isearch-mode.
(interactive "P\np")
(isearch-mode t nil nil (not no-recursive-edit) (null not-word)))
-(defun isearch-forward-symbol (&optional not-symbol no-recursive-edit)
+(defun isearch-forward-symbol (&optional _not-symbol no-recursive-edit)
"Do incremental search forward for a symbol.
The prefix argument is currently unused.
Like ordinary incremental search except that your input is treated
(interactive "P\np")
(isearch-mode nil (null not-regexp) nil (not no-recursive-edit)))
+(defun isearch-forward-symbol-at-point ()
+ "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)
+ (isearch-forward-symbol nil 1)
+ (let ((bounds (find-tag-default-bounds)))
+ (cond
+ (bounds
+ (when (< (car bounds) (point))
+ (goto-char (car bounds)))
+ (isearch-yank-string
+ (buffer-substring-no-properties (car bounds) (cdr bounds))))
+ (t
+ (setq isearch-error "No symbol at point")
+ (isearch-update)))))
+
\f
;; isearch-mode only sets up incremental search for the minor mode.
;; All the work is done by the isearch-mode commands.
(setq minibuffer-message-timeout isearch-original-minibuffer-message-timeout)
(isearch-dehighlight)
(lazy-highlight-cleanup lazy-highlight-cleanup)
- (let ((found-start (window-start (selected-window)))
+ (let ((found-start (window-start))
(found-point (point)))
(when isearch-window-configuration
(set-window-configuration isearch-window-configuration)
"Return a regexp which matches words, ignoring punctuation.
Given STRING, a string of words separated by word delimiters,
compute a regexp that matches those exact words separated by
-arbitrary punctuation. If LAX is non-nil, the end of the string
-need not match a word boundary unless it ends in whitespace.
+arbitrary punctuation. If the string begins or ends in whitespace,
+the beginning or the end of the string matches arbitrary whitespace.
+Otherwise if LAX is non-nil, the beginning or the end of the string
+need not match a word boundary.
Used in `word-search-forward', `word-search-backward',
`word-search-forward-lax', `word-search-backward-lax'."
- (if (string-match-p "^\\W*$" string)
- ""
- (concat
- "\\b"
- (mapconcat 'identity (split-string string "\\W+" t) "\\W+")
- (if (or (not lax) (string-match-p "\\W$" string)) "\\b"))))
+ (cond
+ ((equal string "") "")
+ ((string-match-p "\\`\\W+\\'" string) "\\W+")
+ (t (concat
+ (if (string-match-p "\\`\\W" string) "\\W+"
+ (unless lax "\\<"))
+ (mapconcat 'regexp-quote (split-string string "\\W+" t) "\\W+")
+ (if (string-match-p "\\W\\'" string) "\\W+"
+ (unless lax "\\>"))))))
(defun word-search-backward (string &optional bound noerror count)
"Search backward from point for STRING, ignoring differences in punctuation.
(defun isearch-symbol-regexp (string &optional lax)
"Return a regexp which matches STRING as a symbol.
Creates a regexp where STRING is surrounded by symbol delimiters \\_< and \\_>.
-If LAX is non-nil, the end of the string need not match a symbol boundary."
- (concat "\\_<" (regexp-quote string) (unless lax "\\_>")))
+If there are more than one symbol, then compute a regexp that matches
+those exact symbols separated by non-symbol characters. If the string
+begins or ends in whitespace, the beginning or the end of the string
+matches arbitrary non-symbol whitespace. Otherwise if LAX is non-nil,
+the beginning or the end of the string need not match a symbol boundary."
+ (let ((not-word-symbol-re
+ ;; This regexp matches all syntaxes except word and symbol syntax.
+ ;; FIXME: Replace it with something shorter if possible (bug#14602).
+ "\\(?:\\s-\\|\\s.\\|\\s(\\|\\s)\\|\\s\"\\|\\s\\\\|\\s/\\|\\s$\\|\\s'\\|\\s<\\|\\s>\\|\\s@\\|\\s!\\|\\s|\\)+"))
+ (cond
+ ((equal string "") "")
+ ((string-match-p (format "\\`%s\\'" not-word-symbol-re) string) not-word-symbol-re)
+ (t (concat
+ (if (string-match-p (format "\\`%s" not-word-symbol-re) string) not-word-symbol-re
+ (unless lax "\\_<"))
+ (mapconcat 'regexp-quote (split-string string not-word-symbol-re t) not-word-symbol-re)
+ (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 ")
"Start `query-replace' with string to replace from last search string.
The arg DELIMITED (prefix arg if interactive), if non-nil, means replace
only matches surrounded by word boundaries. Note that using the prefix arg
-is possible only when `isearch-allow-scroll' is non-nil, and it doesn't
-always provide the correct matches for `query-replace', so the preferred
-way to run word replacements from Isearch is `M-s w ... M-%'."
+is possible only when `isearch-allow-scroll' is non-nil or
+`isearch-allow-prefix' is non-nil, and it doesn't always provide the
+correct matches for `query-replace', so the preferred way to run word
+replacements from Isearch is `M-s w ... M-%'."
(interactive
(list current-prefix-arg))
(barf-if-buffer-read-only)
(query-replace-read-to
isearch-string
(concat "Query replace"
- (if (or delimited isearch-word) " word" "")
+ (if (or delimited isearch-word)
+ (let* ((symbol (or delimited isearch-word))
+ (string (and symbol (symbolp symbol)
+ (get symbol 'isearch-message-prefix))))
+ (if (stringp string)
+ ;; Move space from the end to the beginning.
+ (replace-regexp-in-string "\\(.*\\) \\'" " \\1" string)
+ " word"))
+ "")
(if isearch-regexp " regexp" "")
(if (and transient-mark-mode mark-active) " in region" ""))
isearch-regexp)
;; No subexpression so collect the entire match.
"\\&"
;; Get the regexp for collection pattern.
- (isearch-done nil t)
- (isearch-clean-overlays)
- (let ((default (car occur-collect-regexp-history)))
- (read-regexp
- (format "Regexp to collect (default %s): " default)
- default 'occur-collect-regexp-history)))
+ (let ((default (car occur-collect-regexp-history))
+ regexp-collect)
+ (with-isearch-suspended
+ (setq regexp-collect
+ (read-regexp
+ (format "Regexp to collect (default %s): " default)
+ default 'occur-collect-regexp-history)))
+ regexp-collect))
;; Otherwise normal occur takes numerical prefix argument.
(when current-prefix-arg
(prefix-numeric-value current-prefix-arg))))))
(isearch-done nil t)
(isearch-clean-overlays))
(require 'hi-lock nil t)
- (let ((string (cond (isearch-regexp isearch-string)
+ (let ((regexp (cond ((functionp isearch-word)
+ (funcall isearch-word isearch-string))
+ (isearch-word (word-search-regexp isearch-string))
+ (isearch-regexp isearch-string)
((if (and (eq isearch-case-fold-search t)
search-upper-case)
(isearch-no-upper-case-p
(regexp-quote s))))
isearch-string ""))
(t (regexp-quote isearch-string)))))
- (hi-lock-face-buffer string (hi-lock-read-face-name)))
+ (hi-lock-face-buffer regexp (hi-lock-read-face-name)))
(and isearch-recursive-edit (exit-recursive-edit)))
\f
(interactive "p")
(if (= 0 (length isearch-string))
(ding)
- (setq isearch-string (substring isearch-string 0 (- (or arg 1)))
+ (setq isearch-string (substring isearch-string 0
+ (- (min (or arg 1)
+ (length isearch-string))))
isearch-message (mapconcat 'isearch-text-char-description
isearch-string "")))
;; Use the isearch-other-end as new starting point to be able
;; to find the remaining part of the search string again.
+ ;; This is like what `isearch-search-and-update' does,
+ ;; but currently it doesn't support deletion of characters
+ ;; for the case where unsuccessful search may become successful
+ ;; by deletion of characters.
(if isearch-other-end (goto-char isearch-other-end))
(isearch-search)
(isearch-push-state)
(forward-word 1))
(forward-char 1)) (point))))
-(defun isearch-yank-word ()
+(defun isearch-yank-word (&optional arg)
"Pull next word from buffer into search string."
- (interactive)
- (isearch-yank-internal (lambda () (forward-word 1) (point))))
+ (interactive "p")
+ (isearch-yank-internal (lambda () (forward-word arg) (point))))
-(defun isearch-yank-line ()
+(defun isearch-yank-line (&optional arg)
"Pull rest of line from buffer into search string."
- (interactive)
+ (interactive "p")
(isearch-yank-internal
(lambda () (let ((inhibit-field-text-motion t))
- (line-end-position (if (eolp) 2 1))))))
+ (line-end-position (if (eolp) (1+ arg) arg))))))
-(defun isearch-char-by-name ()
+(defun isearch-char-by-name (&optional count)
"Read a character by its Unicode name and add it to the search string.
-Completion is available like in `read-char-by-name' used by `insert-char'."
- (interactive)
+Completion is available like in `read-char-by-name' used by `insert-char'.
+With argument, add COUNT copies of the character."
+ (interactive "p")
(with-isearch-suspended
(let ((char (read-char-by-name "Add character to search (Unicode name or hex): ")))
(when char
- (setq isearch-new-string (concat isearch-string (string char))
- isearch-new-message (concat isearch-message
- (mapconcat 'isearch-text-char-description
- (string char) "")))))))
+ (let ((string (if (and (integerp count) (> count 1))
+ (make-string count char)
+ (char-to-string char))))
+ (setq isearch-new-string (concat isearch-string string)
+ isearch-new-message (concat isearch-message
+ (mapconcat 'isearch-text-char-description
+ string ""))))))))
(defun isearch-search-and-update ()
;; Do the search and update the display.
:type 'boolean
:group 'isearch)
+(defcustom isearch-allow-prefix t
+ "Whether prefix arguments are allowed during incremental search.
+If non-nil, entering a prefix argument will not terminate the
+search. This option is ignored \(presumed t) when
+`isearch-allow-scroll' is set."
+ :version "24.4"
+ :type 'boolean
+ :group 'isearch)
+
(defun isearch-string-out-of-window (isearch-point)
"Test whether the search string is currently outside of the window.
Return nil if it's completely visible, or if point is visible,
Return the key sequence as a string/vector."
(isearch-unread-key-sequence keylist)
(let (overriding-terminal-local-map)
- (read-key-sequence nil))) ; This will go through function-key-map, if nec.
+ ;; This will go through function-key-map, if nec.
+ ;; The arg DONT-DOWNCASE-LAST prevents premature shift-translation.
+ (read-key-sequence nil nil t)))
(defun isearch-lookup-scroll-key (key-seq)
"If KEY-SEQ is bound to a scrolling command, return it as a symbol.
(lookup-key local-function-key-map key)))
(while keylist
(setq key (car keylist))
+ ;; Handle an undefined shifted printing character
+ ;; by downshifting it if that makes it printing.
+ ;; (As read-key-sequence would normally do,
+ ;; if we didn't have a default definition.)
+ (if (and (integerp key)
+ (memq 'shift (event-modifiers key))
+ (>= key (+ ?\s (- ?\S-a ?a)))
+ (/= key (+ 127 (- ?\S-a ?a)))
+ (< key (+ 256 (- ?\S-a ?a))))
+ (setq key (- key (- ?\S-a ?a))))
;; If KEY is a printing char, we handle it here
;; directly to avoid the input method and keyboard
;; coding system translating it.
(setq prefix-arg arg)
(apply 'isearch-unread keylist)
(isearch-edit-string))
- ;; Handle a scrolling function.
- ((and isearch-allow-scroll
- (progn (setq key (isearch-reread-key-sequence-naturally keylist))
- (setq keylist (listify-key-sequence key))
- (setq main-event (aref key 0))
- (setq scroll-command (isearch-lookup-scroll-key key))))
+ ;; Handle a scrolling function or prefix argument.
+ ((progn
+ (setq key (isearch-reread-key-sequence-naturally keylist)
+ keylist (listify-key-sequence key)
+ main-event (aref key 0))
+ (or (and isearch-allow-scroll
+ (setq scroll-command (isearch-lookup-scroll-key key)))
+ (and isearch-allow-prefix
+ (let (overriding-terminal-local-map)
+ (setq scroll-command (key-binding key))
+ (memq scroll-command
+ '(universal-argument
+ negative-argument digit-argument))))))
;; From this point onwards, KEY, KEYLIST and MAIN-EVENT hold a
;; complete key sequence, possibly as modified by function-key-map,
;; not merely the one or two event fragment which invoked
(isearch-unread-key-sequence keylist)
(setq main-event (car unread-command-events))
+ ;; Don't store special commands in the keyboard macro.
+ (let (overriding-terminal-local-map)
+ (when (memq (key-binding key)
+ '(kmacro-start-macro
+ kmacro-end-macro kmacro-end-and-call-macro))
+ (cancel-kbd-macro-events)))
+
;; If we got a mouse click event, that event contains the
;; window clicked on. maybe it was read with the buffer
;; it was clicked on. If so, that buffer, not the current one,
(t;; otherwise nil
(isearch-process-search-string key key)))))
-(defun isearch-quote-char ()
- "Quote special characters for incremental search."
- (interactive)
+(defun isearch-quote-char (&optional count)
+ "Quote special characters for incremental search.
+With argument, add COUNT copies of the character."
+ (interactive "p")
(let ((char (read-quoted-char (isearch-message t))))
;; Assume character codes 0200 - 0377 stand for characters in some
;; single-byte character set, and convert them to Emacs
(if (and isearch-regexp isearch-regexp-lax-whitespace (= char ?\s))
(if (subregexp-context-p isearch-string (length isearch-string))
(isearch-process-search-string "[ ]" " ")
- (isearch-process-search-char char))
- (and enable-multibyte-characters
- (>= char ?\200)
- (<= char ?\377)
- (setq char (unibyte-char-to-multibyte char)))
- (isearch-process-search-char char))))
-
-(defun isearch-printing-char ()
- "Add this ordinary printing character to the search string and search."
- (interactive)
- (let ((char last-command-event))
+ (isearch-process-search-char char count))
+ ;; This used to assume character codes 0240 - 0377 stand for
+ ;; characters in some single-byte character set, and converted them
+ ;; to Emacs characters. But in 23.1 this feature is deprecated
+ ;; in favor of inserting the corresponding Unicode characters.
+ ;; (and enable-multibyte-characters
+ ;; (>= char ?\200)
+ ;; (<= char ?\377)
+ ;; (setq char (unibyte-char-to-multibyte char)))
+ (isearch-process-search-char char count))))
+
+(defun isearch-printing-char (&optional char count)
+ "Add this ordinary printing CHAR to the search string and search.
+With argument, add COUNT copies of the character."
+ (interactive (list last-command-event
+ (prefix-numeric-value current-prefix-arg)))
+ (let ((char (or char last-command-event)))
(if (= char ?\S-\ )
(setq char ?\s))
(if current-input-method
- (isearch-process-search-multibyte-characters char)
- (isearch-process-search-char char))))
+ (isearch-process-search-multibyte-characters char count)
+ (isearch-process-search-char char count))))
-(defun isearch-process-search-char (char)
+(defun isearch-process-search-char (char &optional count)
;; * and ? are special in regexps when not preceded by \.
;; } and | are special in regexps when preceded by \.
;; Nothing special for + because it matches at least once.
((eq char ?\}) (isearch-fallback t t))
((eq char ?|) (isearch-fallback t nil t)))
- ;; Append the char to the search string, update the message and re-search.
- (isearch-process-search-string
- (char-to-string char)
- (if (>= char ?\200)
- (char-to-string char)
- (isearch-text-char-description char))))
+ ;; Append the char(s) to the search string,
+ ;; update the message and re-search.
+ (let* ((string (if (and (integerp count) (> count 1))
+ (make-string count char)
+ (char-to-string char)))
+ (message (if (>= char ?\200)
+ string
+ (mapconcat 'isearch-text-char-description string ""))))
+ (isearch-process-search-string string message)))
(defun isearch-process-search-string (string message)
(setq isearch-string (concat isearch-string string)
(< (point) isearch-opoint)))
"over")
(if isearch-wrapped "wrapped ")
- (mapconcat (lambda (s)
- (and (symbolp s)
- (get s 'isearch-message-prefix)))
- (if (consp isearch-filter-predicates)
- isearch-filter-predicates
- (list isearch-filter-predicates))
- "")
+ (let ((prefix ""))
+ (advice-function-mapc
+ (lambda (_ props)
+ (let ((np (cdr (assq 'isearch-message-prefix props))))
+ (if np (setq prefix (concat np prefix)))))
+ isearch-filter-predicate)
+ prefix)
(if isearch-word
(or (and (symbolp isearch-word)
(get isearch-word 'isearch-message-prefix))
(if (or (not isearch-success)
(bobp) (eobp)
(= (match-beginning 0) (match-end 0))
- ;; When one of filter predicates returns nil,
- ;; retry the search. Otherwise, act according
- ;; to search-invisible (open overlays, etc.)
- (and (run-hook-with-args-until-failure
- 'isearch-filter-predicates
- (match-beginning 0) (match-end 0))
- (or (eq search-invisible t)
- (not (isearch-range-invisible
- (match-beginning 0) (match-end 0))))))
+ (funcall isearch-filter-predicate
+ (match-beginning 0) (match-end 0)))
(setq retry nil)))
(setq isearch-just-started nil)
(if isearch-success
(invalid-regexp
(setq isearch-error (car (cdr lossage)))
- (if (string-match
- "\\`Premature \\|\\`Unmatched \\|\\`Invalid "
- isearch-error)
- (setq isearch-error "incomplete input")))
+ (cond
+ ((string-match
+ "\\`Premature \\|\\`Unmatched \\|\\`Invalid "
+ isearch-error)
+ (setq isearch-error "incomplete input"))
+ ((and (not isearch-regexp)
+ (string-match "\\`Regular expression too big" isearch-error))
+ (cond
+ (isearch-word
+ (setq isearch-error "Too many words"))
+ ((and isearch-lax-whitespace search-whitespace-regexp)
+ (setq isearch-error "Too many spaces for whitespace matching"))))))
(search-failed
(setq isearch-success nil)
searched too when `search-invisible' is t."
(or (eq search-invisible t)
(not (isearch-range-invisible beg end))))
-(make-obsolete 'isearch-filter-visible 'isearch-invisible "24.4")
\f
;; General utilities
(if (or (not success)
(= (point) bound) ; like (bobp) (eobp) in `isearch-search'.
(= (match-beginning 0) (match-end 0))
- (and (run-hook-with-args-until-failure
- 'isearch-filter-predicates
- (match-beginning 0) (match-end 0))
- (not (isearch-range-invisible
- (match-beginning 0) (match-end 0)))))
+ (funcall isearch-filter-predicate
+ (match-beginning 0) (match-end 0)))
(setq retry nil)))
success)
(error nil)))