Subject: bug#14742: 24.3.50; enhancement request: be able to prepend stuff from buffer when search backward

From: Juri Linkov <[email-masked]>
Date: Sat, 06 Jul 2013 01:28:15 +0300

> Could you move this new code to a separate function?

In the following patch I moved new code to a separate function
`isearch-yank-or-del' that works like a combination of the existing
functions `isearch-yank-string' and `isearch-del-char'.
`isearch-yank-or-del' is general enough that it can be used
separately from the movement-yanking feature.

The remaining code in `isearch-other-meta-char' is also general enough
that it can be used to implement other features as well.  For example,
I tried to replace the call `(isearch-yank-or-del (point) new-point)'
with `(goto-char new-point)' and got another useful fully working feature
mentioned by Drew in this thread where cursor movement doesn't yank text.

This raises the question how users would be able to select
between these alternatives.  Maybe `isearch-allow-move'
should provide two options:

- `jump' that doesn't yank text during cursor movement;

- `yank' to yank text during cursor movement.

These options could be implemented in the next patch,
whereas this patch just moves yanking/deleting to a new function
`isearch-yank-or-del'.

=== modified file 'lisp/isearch.el'
--- lisp/isearch.el	2013-06-13 22:08:45 +0000
+++ lisp/isearch.el	2013-07-05 22:27:08 +0000
@@ -987,6 +1046,8 @@ (defun isearch-update ()
   ;; We must prevent the point moving to the end of composition when a
   ;; part of the composition has just been searched.
   (setq disable-point-adjustment t)
+  (when (eq (car-safe isearch-allow-move) 'only)
+    (setq isearch-allow-move (cdr isearch-allow-move)))
   (run-hooks 'isearch-update-post-hook))
 
 (defun isearch-done (&optional nopush edit)
@@ -1969,6 +2142,56 @@ (defun isearch-yank-line (&optional arg)
    (lambda () (let ((inhibit-field-text-motion t))
 		(line-end-position (if (eolp) (1+ arg) arg))))))
 
+(defun isearch-yank-or-del (old-point new-point)
+  "Add or delete text in search string, depending on two given positions.
+Given an old position of point in OLD-POINT and a new position NEW-POINT,
+either pull text from the buffer between these positions into the search
+string, or delete text from the search string to the length of the text
+between these positions.
+In forward search, when NEW-POINT is greater than OLD-POINT,
+yank text from the buffer between NEW-POINT and OLD-POINT;
+when NEW-POINT is less than OLD-POINT, delete text from
+the search string between NEW-POINT and OLD-POINT.
+In reverse search, when NEW-POINT is greater than OLD-POINT,
+delete text from the search string between NEW-POINT and OLD-POINT;
+when NEW-POINT is less than OLD-POINT, yank text from the buffer
+between NEW-POINT and OLD-POINT.
+When NEW-POINT is less than the beginning of the currently matched string
+in forward search, then change search direction to reverse search.
+When NEW-POINT is greater than the end of the currently matched string
+in reverse search, then change search direction to forward search."
+  ;; Change search direction between forward and backward.
+  (when (if isearch-other-end
+	    (and (not isearch-error)	; for regexp incomplete input
+		 (if isearch-forward
+		     (< new-point isearch-other-end)
+		   (> new-point isearch-other-end)))
+	  (if isearch-forward
+	      (< new-point old-point)
+	    (> new-point old-point)))
+    (setq isearch-forward (not isearch-forward))
+    (setq isearch-string "" isearch-message "")
+    (if isearch-other-end (setq old-point isearch-other-end)))
+  (if (< old-point new-point)
+      (if isearch-forward
+	  ;; Add text to the search string.
+	  (isearch-yank-string
+	   (buffer-substring-no-properties old-point new-point))
+	;; In reverse search, lax whitespace doesn't work,
+	;; so go to the new point explicitly.
+	(goto-char new-point)
+	;; In reverse search, delete text from beginning of search string.
+	(isearch-del-char (- new-point old-point) t))
+    (if isearch-forward
+	;; Delete text from the search string.
+	(isearch-del-char (- old-point new-point))
+      ;; In reverse search, lax whitespace doesn't work,
+      ;; so go to the new point explicitly.
+      (goto-char new-point)
+      ;; In reverse search, add text to beginning of search string.
+      (isearch-yank-string
+       (buffer-substring-no-properties old-point new-point) t))))
+
 (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'.
@@ -2267,6 +2483,77 @@ (defun isearch-lookup-scroll-key (key-se
 	     (eq (get binding 'scroll-command) t))
          binding)))
 
+(defcustom isearch-allow-move nil
+  "Whether cursor movement is allowed to yank text in Isearch mode.
+If nil, point motion commands will exit Isearch mode immediately.
+If non-nil, point motion commands extend Isearch by yanking the text
+the cursor moves over in the buffer.  E.g., `C-f' yanks the next char,
+`C-M-f' yanks the next expression, etc. to the end of the search string
+in forward search.  In reverse search, `C-b' yanks the previous char,
+`C-M-b' yanks the previous expression, etc. to the beginning of the
+search string.  And conversely, backward cursor movements, e.g., `C-b'
+deletes text from the end of the search string when searching forward.
+When searching backward, forward cursor movements, e.g., `C-f' deletes
+text from the beginning of the search string.
+
+Lisp programs may give this variable a special value of
+\(only . OLDVAL) to enable motion yanking temporarily.
+After any subsequent Isearch command that is not point motion,
+the value of `isearch-allow-move' is set to OLDVAL."
+  :type 'boolean
+  :version "24.4"
+  :group 'isearch)
+
 (defalias 'isearch-other-control-char 'isearch-other-meta-char)
 
 (defun isearch-other-meta-char (&optional arg)
@@ -2292,7 +2579,7 @@ (defun isearch-other-meta-char (&optiona
                 (this-command-keys)))
 	 (main-event (aref key 0))
 	 (keylist (listify-key-sequence key))
-         scroll-command isearch-point)
+         scroll-command move-command isearch-point)
     (cond ((and (= (length key) 1)
 		(let ((lookup (lookup-key local-function-key-map key)))
 		  (not (or (null lookup) (integerp lookup)
@@ -2379,7 +2666,35 @@ (defun isearch-other-meta-char (&optiona
              (if ab-bel
                  (isearch-back-into-window (eq ab-bel 'above) isearch-point)
                (goto-char isearch-point)))
           (isearch-update))
+	  ;; Handle a motion function.
+	  ((and isearch-allow-move
+		(progn (setq key (isearch-reread-key-sequence-naturally keylist))
+		       (setq keylist (listify-key-sequence key))
+		       (setq main-event (aref key 0))
+		       (setq move-command (or
+					   (isearch-lookup-move-key key)
+					   (isearch-lookup-move-key
+					    ;; Use the last key in the sequence.
+					    (vector (aref key (1- (length key)))))))))
+	   (setq prefix-arg arg)
+	   (let ((new-point (save-excursion
+			      (condition-case ()
+				  (command-execute move-command)
+				(error nil))
+			      (point))))
+	     ;; An alternative to move without yanking is (goto-char new-point)
+	     (isearch-yank-or-del (point) new-point))
+	   (unless (eq (car-safe isearch-allow-move) 'only)
+	     (setq isearch-allow-move (cons 'only isearch-allow-move))))
 	  ;; 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))))