From: Drew Adams <drew.adams@oracle.com>
To: Juri Linkov <juri@linkov.net>
Cc: Karl Fogel <kfogel@red-bean.com>, Emacs developers <emacs-devel@gnu.org>
Subject: RE: PATCH: isearch-yank-until-match
Date: Tue, 20 Aug 2019 15:37:13 -0700 (PDT) [thread overview]
Message-ID: <f9d09bd0-1d5c-4966-9efd-431db2721e3f@default> (raw)
[-- Attachment #1: Type: text/plain, Size: 7125 bytes --]
Sorry this message is long. I've tried to make it
clear.
> C-s C-w C-r C-w messes up the search string. But I
> believe this could be fixed.
Thx. The attached patch takes care of this, I think.
> > Which part(s) do you think should be only optional?
>
> Changing the behavior of yanking in backward search.
> C-r C-w is still useful to yank forward like it does now.
> I don't remember when needed to yank backward.
> Do you use backward yanking often?
This message tries to make things clearer. See the
attached patch for fixes and related new features.
[As for whether I "use backward yanking often", the
answer is no, because it isn't available! ;-) But when
searching backward it makes sense to be able to yank
buffer text to the left of the search point, prepending
(not appending) it to the search string.]
---
I see 3 kinds of "yanking" to the search string (there
may be more):
1. Yank an arbitrary string.
Examples:
* text from the kill-ring
* secondary-selection text
* text from a register
2. Yank consecutive text at the search point from the
buffer.
Examples:
* successive chars
* successive words
* successive lines
* successive chars up to a particular char
3. Yank buffer text from the search point up to
an arbitrary buffer position.
Examples:
* text up to the destination of a movement
command/key
* text up to the position resulting from a
recursive edit
In general, these call for, or invite, three different
behaviors:
1. An arbitrary string should always be pulled onto
the end of the search string, regardless of the
current search direction.
2. Consecutive text at point should be pulled onto the
front of the search string, where "front" is in the
search direction. This means prepend the text when
searching backward and append it when searching
forward.
(If you want to grab both text that precedes the
search hit and text that succeeds it then you need
to change search directions between the two grabs.)
3. When buffer text from the search point to an
arbitrary position is involved we should let that
position adjust the search string, to either expand
or reduce it.
That is, we should not limit such adjustment to
extension; we should let the arbitrary position
determine whether to extend or retract, as follows:
* If the position is outside the search hit then
extend the search string to add the buffer text
from the hit to the position.
* If the position is inside the search hit then
reduce the search string to remove the buffer
text from the hit to the position.
Search direction remains the same, in any case.
(That means that, unlike case #2, you don't need to
change search direction to grab text both left and
right of the search hit.)
For case #2, you've asked that respecting search
direction be made optional. OK, the attached patch
does that (option `isearch-directional-yank').
I don't think there's a good reason not to respect
search direction by default, however, so the option
value respects direction by default. (Just one
opinion.)
[FWIW, I think that unless search-direction is
respected (prepend/append), yanking text that is
consecutive in the forward direction when searching
backward is just, well, broken/wrong.
That it has always been broken (so no one uses it) is
not a good reason to keep the old, wrong behavior by
default. The real right thing is to not bother with
the option and just always respect search direction
for this case #2. But I've respected your request.]
The attached patch does those things. Following case
#2, it makes these commands respect search direction,
provided option `isearch-directional-yank' is non-nil:
`isearch-yank-word-or-char'
`isearch-yank-char'
`isearch-yank-word'
`isearch-yank-line',
`isearch-yank-until-char'
`isearch-yank-until-match'
And following case #3, it defines these commands that
adjust the search string according to an arbitrary
buffer position (inside or outside, cursor movement
left or right from the search point):
`isearch-yank-to-key-destination'
`isearch-yank-through-move'
The new commands are these:
* `isearch-yank-until-char': A version of Karl's
command that respects `isearch-directional-yank'.
* `isearch-yank-until-match' (mentioned previously):
Yank text up through a match for another pattern.
* `isearch-yank-to-key-destination': A version of the
imagined command we discussed that lets you use a
movement key (e.g. `M-f', `C-b') to adjust the search
string.
But it works whether the key moves point left or
right, and whether the destination is inside or
outside the search hit.
In addition, you can use a prefix arg with the
command. The prefix arg is transferred to the
movement key.
* `isearch-yank-through-move': Enters a recursive edit,
where you can move the cursor any way you like, any
number of times, and then exit the recursive edit.
The command adjusts the search string according to
the resulting cursor position.
The last two allow other action, besides just cursor
movement. I think this is generally a plus, but I can
imagine that someone might disagree.
(In that case, `isearch-yank-to-key-destination' could
bind `buffer-read-only', to prevent buffer changes.
But there's no way to control what a user might do in a
recursive edit, AFAIK. E.g., there doesn't seem to be
any hook that's used when entering or exiting a
recursive edit.)
A macro, `define-isearch-yank-movement-command', is
used to define the last two. Their only differences
are:
* `isearch-yank-to-key-destination' reads a key,
inhibits field-text motion, disables bindings of the
Isearch keymap, and invokes the key's command.
* `isearch-yank-through-move' calls `recursive-edit'.
As for key bindings, here are my thoughts:
* `C-M-.' for `isearch-yank-until-char'.
* `C-M-m' (aka `M-RET') for `isearch-yank-until-match'.
* `C-M-c' for `isearch-yank-through-move' (_not_ for
`isearch-yank-until-char').
`C-M-c' is bound globally to `exit-recursive-edit'.
It's how you tell `isearch-yank-through-move' that
you're done moving the cursor. It helps to use the
same key to start and end the command.
And because of that global binding, no (other)
Isearch command should be bound to `C-M-c'. (I
hadn't considered this before, when I said I was OK
with `C-M-c' for `isearch-yank-until-char'.)
The only binding I feel strongly about is `C-M-c'. I
don't have a suggestion yet about a binding for
`isearch-yank-to-key-destination'.
___
P.S. The patch also renames the parameter to
`isearch-yank-internal' from JUMPFORM to JUMPFUN,
because it's a function, not a form. And it fixes
the doc string accordingly: "function", not
necessarily "lambda expression".
[-- Attachment #2: isearch-2019-08-19c.patch --]
[-- Type: application/octet-stream, Size: 16150 bytes --]
diff -u isearch-2019-08-19a.el isearch-2019-08-19c-PATCH.el
--- isearch-2019-08-19a.el 2019-08-18 17:10:13.609445800 -0700
+++ isearch-2019-08-19c-PATCH.el 2019-08-20 14:52:42.410184900 -0700
@@ -173,6 +173,10 @@
command history."
:type 'boolean)
+(defcustom isearch-directional-yank t
+ "Non-nil if yanking consecutive text at point respects search direction."
+ :type 'boolean)
+
(defvar isearch-mode-hook nil
"Function(s) to call after starting up an incremental search.")
@@ -706,6 +710,9 @@
(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 (kbd "C-M-.") 'isearch-yank-until-char)
+ (define-key map (kbd "C-M-m") 'isearch-yank-until-match)
+ (define-key map (kbd "C-M-c") 'isearch-yank-through-move)
(define-key map "\M-s\M-<" 'isearch-beginning-of-buffer)
(define-key map "\M-s\M->" 'isearch-end-of-buffer)
@@ -1731,6 +1738,64 @@
(isearch-abort) ;; outside of let to restore outside global values
)))
+(defmacro define-isearch-yank-movement-command (command arguments doc-string
+ interactive
+ bindings action)
+ "Define Isearch COMMAND to adjust search string based on cursor position.
+The command should move the cursor, which is at one end of the current
+search hit, to a new location.
+
+The existing search string is expanded or reduced to include the
+buffer text from the search hit through the new cursor position.
+
+ARGUMENTS is a list of arguments to the command.
+DOC-STRING is the command's doc string.
+INTERACTIVE is `interative' form.
+BINDINGS is a list of `let*' bindings added around the command code.
+ Local variable `isearch-new-position' is also bound, before the
+ BINDINGS you provide - see ACTION, below.
+BINDINGS is macroexpanded, so it can also be a macro call that expands
+to a list of bindings.
+
+ACTION is your code that moves the cursor. It should set variable
+`isearch-new-position' to the new cursor position."
+ (let ((fwd (make-symbol "fwd"))
+ (beg (make-symbol "beg"))
+ (end (make-symbol "end"))
+ (min-be (make-symbol "min-be"))
+ (max-be (make-symbol "max-be")))
+ `(defun ,command ,arguments ,doc-string
+ ,interactive
+ (let ((,fwd isearch-forward)
+ (,beg isearch-other-end)
+ (,end (point))
+ isearch-new-position)
+ (let* ,bindings
+ ,action
+ (let ((,min-be (min ,beg ,end))
+ (,max-be (max ,beg ,end)))
+ (setq isearch-string (if (< isearch-new-position ,beg)
+ (buffer-substring
+ (min isearch-new-position ,max-be)
+ (max isearch-new-position ,max-be))
+ (buffer-substring
+ (min isearch-new-position ,min-be)
+ (max isearch-new-position ,min-be)))
+ isearch-message (mapconcat 'isearch-text-char-description
+ isearch-string "")
+ isearch-barrier (if (or (and ,fwd
+ (< isearch-new-position ,beg))
+ (and (not ,fwd)
+ (not (< isearch-new-position ,beg))))
+ ,end
+ isearch-new-position)
+ isearch-other-end (if (< isearch-new-position ,beg)
+ (if ,fwd isearch-new-position ,beg)
+ (if (not ,fwd) isearch-new-position ,beg)))
+ (goto-char isearch-barrier)
+ (isearch-highlight isearch-other-end isearch-barrier)
+ (when isearch-lazy-highlight (isearch-lazy-highlight-new-loop))))))))
+
(defvar minibuffer-history-symbol) ;; from external package gmhist.el
(defun isearch-edit-string ()
@@ -2447,8 +2512,9 @@
(isearch-push-state)
(isearch-update))
-(defun isearch-yank-string (string)
- "Pull STRING into search string."
+(defun isearch-yank-string (string &optional respect-direction)
+ "Pull STRING into search string.
+Non-nil RESPECT-DIRECTION means prepend STRING if searching backward."
;; Downcase the string if not supposed to case-fold yanked strings.
(if (and isearch-case-fold-search
(eq 'not-yanks search-upper-case))
@@ -2456,8 +2522,9 @@
(if isearch-regexp (setq string (regexp-quote string)))
;; Don't move cursor in reverse search.
(setq isearch-yank-flag t)
- (isearch-process-search-string
- string (mapconcat 'isearch-text-char-description string "")))
+ (isearch-process-search-string string
+ (mapconcat 'isearch-text-char-description string "")
+ respect-direction))
(defun isearch-yank-kill ()
"Pull string from kill ring into search string."
@@ -2508,17 +2575,18 @@
(interactive)
(isearch-yank-string (xterm--pasted-text)))
-(defun isearch-yank-internal (jumpform)
- "Pull the text from point to the point reached by JUMPFORM.
-JUMPFORM is a lambda expression that takes no arguments and returns
-a buffer position, possibly having moved point to that position.
-For example, it might move point forward by a word and return point,
-or it might return the position of the end of the line."
+(defun isearch-yank-internal (jumpfun &optional respect-direction)
+ "Pull the text from point to the point reached by JUMPFUN.
+JUMPFUN is a function that takes no arguments and returns a buffer
+position, possibly having moved point to that position.
+
+For example, JUMPFUN might move forward by a word and return point, or
+it might return the position of the end of the line.
+
+Non-nil RESPECT-DIRECTION means prepend text if searching backward."
(isearch-yank-string
- (save-excursion
- (and (not isearch-forward) isearch-other-end
- (goto-char isearch-other-end))
- (buffer-substring-no-properties (point) (funcall jumpform)))))
+ (save-excursion (buffer-substring-no-properties (point) (funcall jumpfun)))
+ respect-direction))
(defun isearch-yank-char-in-minibuffer (&optional arg)
"Pull next character from buffer into end of search string in minibuffer."
@@ -2531,44 +2599,189 @@
(forward-char arg)))
(defun isearch-yank-char (&optional arg)
- "Pull next character from buffer into search string.
-If optional ARG is non-nil, pull in the next ARG characters."
+ "Pull character from buffer into search string.
+If `isearch-directional-yank' is non-nil then use next char for
+forward search, previous char for backward search.
+
+With a numeric prefix ARG, pull in ARG characters."
(interactive "p")
- (isearch-yank-internal (lambda () (forward-char arg) (point))))
+ (isearch-yank-internal
+ (lambda ()
+ (funcall (if (or isearch-forward (not isearch-directional-yank))
+ #'forward-char
+ #'backward-char)
+ arg)
+ (point))
+ isearch-directional-yank))
-(defun isearch--yank-char-or-syntax (syntax-list fn)
+(defun isearch--yank-char-or-syntax (syntax-list fn &optional respect-direction)
(isearch-yank-internal
(lambda ()
(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))))
+ (if (or isearch-forward (not respect-direction))
+ (forward-char 1)
+ (backward-char 1)))
+ (point))
+ respect-direction))
(defun isearch-yank-word-or-char ()
+ "Pull character or word from buffer into search string.
+If `isearch-directional-yank' is non-nil then yank next one for
+forward search, previous one for backward search."
+ (interactive)
+ (if (or isearch-forward (not isearch-directional-yank))
+ (isearch--yank-char-or-syntax '(?w) 'forward-word isearch-directional-yank)
+ (isearch--yank-char-or-syntax '(?w) 'backward-word 'RESPECT-DIRECTION)))
+
+(defun isearch-yank-word-or-char-forward ()
"Pull next character or word from buffer into search string."
(interactive)
- (isearch--yank-char-or-syntax '(?w) 'forward-word))
+ (isearch--yank-char-or-syntax '(?w) 'forward-word isearch-directional-yank))
-(defun isearch-yank-symbol-or-char ()
- "Pull next character or symbol from buffer into search string."
+(defun isearch-yank-word-or-char-backward ()
+ "Pull previous character or word from buffer into search string."
(interactive)
- (isearch--yank-char-or-syntax '(?w ?_) 'forward-symbol))
+ (isearch--yank-char-or-syntax '(?w) 'backward-word 'RESPECT-DIRECTION))
+
+(defun isearch-yank-symbol-or-char ()
+ "Pull character or symbol from buffer into search string.
+If `isearch-directional-yank' is non-nil then pull next one for
+forward search, previous one for backward search."
+ (interactive)
+ (if (or isearch-forward (not isearch-directional-yank))
+ (isearch--yank-char-or-syntax '(?w ?_) 'forward-symbol isearch-directional-yank)
+ (isearch--yank-char-or-syntax '(?w ?_) 'backward-symbol 'RESPECT-DIRECTION)))
(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."
+ "Pull word from buffer into search string.
+If `isearch-directional-yank' is non-nil then pull next word for
+forward search, previous word for backward search.
+
+With a numeric prefix ARG, pull in ARG words."
(interactive "p")
- (isearch-yank-internal (lambda () (forward-word arg) (point))))
+ (isearch-yank-internal
+ (lambda ()
+ (funcall (if (or isearch-forward (not isearch-directional-yank))
+ #'forward-word
+ #'backward-word)
+ arg)
+ (point))
+ isearch-directional-yank))
(defun isearch-yank-line (&optional arg)
"Pull rest of line from buffer into search string.
-If optional ARG is non-nil, yank the next ARG lines."
+If `isearch-directional-yank' is non-nil then pull in rest of line in
+search direction.
+
+With a numeric prefix ARG, pull in ARG lines."
+ (interactive "p")
+ (if (or isearch-forward (not isearch-directional-yank))
+ (isearch-yank-line-forward arg)
+ (isearch-yank-line-backward arg)))
+
+(defun isearch-yank-line-forward (&optional arg)
+ "Pull rest of line, going forward, from buffer into search string.
+With a numeric prefix ARG, pull in the next ARG lines."
+ (interactive "p")
+ (isearch-yank-internal
+ (lambda ()
+ (let ((inhibit-field-text-motion t))
+ (line-end-position (if (eolp) (1+ arg) arg))))))
+
+(defun isearch-yank-line-backward (&optional arg)
+ "Pull rest of line, going backward, from buffer into search string.
+With a numeric prefix ARG, pull in the previous ARG lines."
(interactive "p")
(isearch-yank-internal
- (lambda () (let ((inhibit-field-text-motion t))
- (line-end-position (if (eolp) (1+ arg) arg))))))
+ (lambda ()
+ (let ((inhibit-field-text-motion t)
+ (arg2 (- 2 arg)))
+ (line-beginning-position (if (bolp) (1- arg2) arg2))))
+ 'RESPECT-DIRECTION))
+
+(defun isearch-yank-until-char (char)
+ "Pull buffer text, up to next instance of CHAR, into search string.
+You are prompted for CHAR."
+ (interactive "cYank until character: ")
+ (isearch-yank-internal
+ (lambda ()
+ (let ((inhibit-field-text-motion t))
+ (funcall (if (or isearch-forward (not isearch-directional-yank))
+ #'search-forward
+ #'search-backward)
+ (char-to-string char))
+ (if isearch-forward (backward-char) (forward-char))
+ (point)))
+ isearch-directional-yank))
+
+(defun isearch-yank-until-match (arg)
+ "Pull text, through match for another pattern, into search string.
+You are prompted for the pattern.
+With a prefix arg, match the pattern as a regexp."
+ (interactive "P")
+ (let ((fwd (or isearch-forward (not isearch-directional-yank)))
+ pattern)
+ (with-isearch-suspended
+ (setq pattern (if arg (read-regexp "Match regexp: ") (read-string "Match: "))))
+ (isearch-yank-internal
+ (lambda ()
+ (let ((inhibit-field-text-motion t))
+ (funcall (if arg
+ (if fwd #'search-forward-regexp #'search-backward-regexp)
+ (if fwd #'search-forward #'search-backward))
+ pattern)
+ (point)))
+ isearch-directional-yank)))
+
+(define-isearch-yank-movement-command isearch-yank-to-key-destination (key)
+ "Adjust search to use text from search hit through a key destination.
+You are prompted for a key sequence that moves the cursor. The key
+can do anything else as well, but only the new cursor position is used
+by the command.
+
+If it makes sense for the key, you can use a prefix arg with `\\<isearch-mode-map>\
+\\[isearch-yank-to-key-destination]'
+to apply the prefix arg to the key. For example, `C-u 5 \
+\\[isearch-yank-to-key-destination] M-f'
+moves the cursor forward 5 words and adjusts the search string
+accordingly.
+
+If the new position is outside the existing search hit then the text
+from the search hit to the new position is added to the search string.
+If the position is inside the hit, then the text from the edge of the
+hit through the new position is removed from the search string."
+ (interactive "kKey sequence (to move cursor): ")
+ ((inhibit-field-text-motion t)
+ (isearch-mode-map nil)
+ (command (or (local-key-binding key t)
+ (global-key-binding key t))))
+ (save-excursion
+ (call-interactively command)
+ (setq isearch-new-position (point))))
+
+(define-isearch-yank-movement-command isearch-yank-through-move ()
+ "Adjust search to use text from search hit through a new cursor position.
+You enter a recursive edit to move the cursor any way you like.
+Use \\[exit-recursive-edit] to resume search with the adjusted search string.
+
+In the recursive edit you can do anything, but the effect used by the
+command is only cursor movement to a new position.
+
+If the new position is outside the existing search hi,t then the text
+from the search hit to the new position is added to the search string.
+If the position is inside the hit, then the text from the edge of the
+hit through the new position is removed from the search string."
+ (interactive)
+ ()
+ (with-isearch-suspended
+ (save-excursion
+ (message (substitute-command-keys
+ "RECURSIVE edit. `\\[exit-recursive-edit]' to resume Isearch"))
+ (recursive-edit)
+ (setq isearch-new-position (point)))))
(defun isearch-char-by-name (&optional count)
"Read a character by its Unicode name and add it to the search string.
@@ -3014,9 +3227,16 @@
(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)
- isearch-message (concat isearch-message message))
+(defun isearch-process-search-string (string message &optional respect-direction)
+ "Add STRING to `isearch-string' and MESSAGE to `isearch-message'.
+Append STRING if searching forward. Prepend if searching backward."
+ (let ((fwd (or isearch-forward (not respect-direction))))
+ (setq isearch-string (if fwd
+ (concat isearch-string string)
+ (concat string isearch-string))
+ isearch-message (if fwd
+ (concat isearch-message message)
+ (concat message isearch-message))))
(isearch-search-and-update))
\f
next reply other threads:[~2019-08-20 22:37 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-08-20 22:37 Drew Adams [this message]
2019-09-16 21:24 ` PATCH: isearch-yank-until-match Drew Adams
2019-09-17 16:03 ` Karl Fogel
-- strict thread matches above, loose matches on Subject: below --
2019-08-14 21:08 Drew Adams
2019-08-15 18:16 ` Juri Linkov
2019-08-15 22:15 ` Drew Adams
2019-08-16 17:51 ` Juri Linkov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=f9d09bd0-1d5c-4966-9efd-431db2721e3f@default \
--to=drew.adams@oracle.com \
--cc=emacs-devel@gnu.org \
--cc=juri@linkov.net \
--cc=kfogel@red-bean.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).