-
Notifications
You must be signed in to change notification settings - Fork 1.3k
avoid infinite loop in replace when empty string matches
#3566
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| searchLoc = start | ||
| } else { | ||
| searchLoc = searchLoc.Move(1, h.Buf) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not do this just immediately after we found and replaced an empty match, rather than postpone it to the next iteration? i.e.
--- a/internal/action/command.go
+++ b/internal/action/command.go
@@ -993,6 +993,14 @@ func (h *BufPane) ReplaceCmd(args []string) {
if end.Y == locs[1].Y {
end = end.Move(nrunes, h.Buf)
}
+ if locs[0] == locs[1] {
+ // advance after empty match
+ if searchLoc == end {
+ searchLoc = start
+ } else {
+ searchLoc = searchLoc.Move(1, h.Buf)
+ }
+ }
h.Cursor.Loc = searchLoc
nreplaced++
} else if !canceled && !yes {There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...Also, this addresses the issue for replace but not for find. I.e.: we press Ctrl-f and type $, the cursor correctly moves to the end of the line, then we press Ctrl-n to find the next match but the cursor stays where it is, rather than moves to the end of the next line.
So shouldn't we move this logic into h.Buf.FindNext() instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also a side note: matching ^ (beginning of the line) still works not quite correctly (for a different reason). Looks like we should handle patterns beginning with ^ in a special way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not do this just immediately after we found and replaced an empty match
That's not the same. For example, saying replace '(z|$)' '@' for
abc
xyz
would give
abc@
xy@@
with your approach, but
abc@
xy@
with replaceall and my variant. Of course, one can implement anything one wants, but I think it would be nice if replace with answer y all the give (until the search wraps over) gives the same result as replaceall.
this addresses the issue for
replacebut not forfind
That's true.
So shouldn't we move this logic into
h.Buf.FindNext()instead?
FindNext itself cannot move the cursor forward. How would FindNext know if an empty match is acceptable? Would that be another argument?
matching
^(beginning of the line) still works not quite correctly
Can you share an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not the same. For example, saying
replace '(z|$)' '@'for [...]
Indeed. Yeah, your version seems better then.
FindNextitself cannot move the cursor forward. How wouldFindNextknow if an empty match is acceptable? Would that be another argument?
Yep, I hadn't thought about the details, I was hoping we could generalize it between "find" and "replace" cases but seems like we need to address them separately...
matching
^(beginning of the line) still works not quite correctlyCan you share an example?
Just replace ^ @, for example. Or e.g. replace ^foo @ when the cursor is at foo but not at the beginning of a line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping we could generalize it between "find" and "replace" cases but seems like we need to address them separately...
If we add a field like LastMatch to Buffer (besides LastSearch), then h.Buf.FindNext() could take this into account. So I now think it could work to make essentially all changes only once in h.Buf.FindNext(). At least it's worth a try.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think anymore that it's a good idea to modify h.Buf.FindNext(). That function may be called from scripts that shouldn't modify the search status of the buffer. One might be able to reuse a modified h.FindNext() in ReplaceCmd. Maybe it's better to do the necessary changes in h.FindNext() first and then see for an overlap.
I've just force-pushed a new version where I've just renamed prevMatchEnd to lastMatchEnd to align it with the naming of variables like LastSearch. Otherwise this PR seems fine to me provided that one only wants to modify ReplaceCmd.
2787000 to
ecdb388
Compare
ecdb388 to
2cd042b
Compare
When the empty string matches during an interactive replacement, then the replacement text will always be inserted at the same spot and the procedure never moves forward. As a result, the user is caught in an infinite loop and has to cancel the command. An example would be something like
replace '$' ','which will add all commas at the end of the current line without moving to the next.This PR fixes this by not allowing an empty match right after a previous match. This is also what
replacealldoes (because it's done in Go's regexp library). As a result,replacebehaves likereplaceall.Well, almost: Currently a command of the form
replace '' 'x'doesn't replace anything, unlikereplaceall '' 'x'. This is caused by the testmicro/internal/buffer/search.go
Lines 110 to 113 in fb20818
If this PR is merged, then one could think about delete this test. Then
replacewould match the behavior ofreplaceall.