-
Notifications
You must be signed in to change notification settings - Fork 1.3k
enhance search API #3658
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
base: master
Are you sure you want to change the base?
enhance search API #3658
Conversation
8b80291 to
92b6fba
Compare
|
I've rebased the PR onto master and added |
92b6fba to
72fcf50
Compare
|
The latest commit fixes a subtle bug related to the padding of the search region: In the presence of combining characters, one could end up with an infinite loop. (Try searching backwards for This bug is also present in #3575, hence in master. If you want, I can backport 88f3cf5 to master. This would require some modification of the commit, so let me know if that's necessary. |
0d14eae to
88f3cf5
Compare
88f3cf5 to
1c1a35a
Compare
|
I've force-pushed a polished version and updated the list of functions at the top of this page. It still fixes the bug mentioned above. Also, locations returned for matches and submatches are now guaranteed to include the runes that matched. The underlying Go regexp functions match runes, which may be part of combining characters like This PR introduces many new functions. Maybe we don't need all of them. For example, do we need a submatch version on top of each non-submatch search or replace function? If we keep only the submatch version, we wouldn't lose any functionality because the additional elements in the slice for each match can be ignored. I nevertheless added all functions to this PR to show what's possible. |
|
@dmaluka Any chance to move forward with this PR? Apart from a detailed review, a quick feedback would be helpful:
|
|
Haven't had much time to look into it yet, sorry. |
|
To see some of the new search functions in action, check out my LaTeX plugin. The way the new |
1c1a35a to
8683a1a
Compare
|
In the latest version, the regexp for all new search and replace functions can be specified either as a |
I'm worried that we are exposing such implementation details as padded regexps as a part of the API. I think we should try and make it a bit more abstract and future-proof, e.g. something like:
And IMHO this implicit polymorphism is messy. I'm thinking of something like: which internally use:
Seems reasonable. The caller can ignore the returned submatches if it doesn't need them.
Seems reasonable. The intuitively expected behavior would be: if And I'm not even sure why exactly we currently swap them. From looking at the code it seems like we already make sure to always pass |
Exactly. Another option would be to combine |
|
Yes, it would be too smart. |
internal/buffer/eventhandler.go
Outdated
| } | ||
|
|
||
| // ExecuteTextEvent runs a text event | ||
| // The deltas are processed in reverse order and afterwards reversed |
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.
Add an explanation why it is needed?
Also, this is rather an implementation detail, so maybe this comment should be inside the function?
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 wouldn't say that it's an implementation detail. When you create a TextEvent t, you have to know if which order the elements of t.Deltas are processed because that changes the meaning of the locations. To keep it less technical, we could say that the locations of the various Deltas have to be (non-overlapping and) in increasing order. The old comment could then indeed move inside the function.
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 wouldn't say that it's an implementation detail. When you create a
TextEventt, you have to know if which order the elements oft.Deltasare processed because that changes the meaning of the locations. To keep it less technical, we could say that the locations of the variousDeltas have to be (non-overlapping and) in increasing order. The old comment could then indeed move inside the function.
So let's do that?
BTW then such comment about non-overlapping and increasing order should be rather not here but in the TextEvent struct definition, and/or in MultipleReplace() - the only actual user of multiple deltas?
...Side note: looking at the wording of the MultipleReplace() description: "MultipleReplace creates an multiple insertions executes them" - ermmhmm...
| Stdout = new(bytes.Buffer) | ||
| } | ||
|
|
||
| // RangeMap returns the slice obtained from applying the given function |
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 SliceMap?
And I'm not sure we even need this helper. I see there is exactly one usage of it, and it doesn't look very convincing. And I'm not sure we should create a precedent of using generics unless we really find it useful.
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 called it RangeMap instead of SliceMap because the function f does not only receive the slice element, but also the position, as in a range.
You are right, this helper function is not used elsewhere at present. Maybe the reason is that it needs type parameters, and before the recent bump from Go 1.17 to 1.19 we didn't have them. I myself am new to Go, and I find it annoying that such basic functionality is not included directly in Go. I'm sure that if we looked through the code for micro, we would find places where RangeMap would be useful. I would be optimistic that there are other uses in the future. I'm using it in another PR that I haven't submitted yet because it depends on the present one. But it's up to you. If you want me to delete it, I'll do it.
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.
Now I actually regret we bumped it from 1.17 to 1.19. We'd have an easy compelling answer to questions "why not use generics", "why not use any", "why not use another shiny new feature X".
internal/buffer/search.go
Outdated
| // ReplaceAllLiteral replaces all matches of the regexp `s` with `repl` in | ||
| // the given area. The function returns the number of replacements made, the | ||
| // new end position and any error that occured during regexp compilation | ||
| func (b *Buffer) ReplaceAllLiteral(s string, start, end Loc, repl []byte) (int, Loc, error) { |
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 pass literal as a boolean argument to ReplaceAll()?
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.
Sure, I can change that. The reason I had chosen ReplaceAllLiteral was to imitate Go's regexp API.
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.
Heh, didn't realize that. Anyway, we don't need to replicate Go's API precisely, we can define whatever API is more convenient for us to use.
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.
What I like about ReplaceAllLiteral is that I don't have to remember whether a second argument true to ReplaceAll means "use as regexp template" or "use literally". (I believe that in almost all practical purposes, this argument would be a constant for each invocation of ReplaceAll, not some variable whose value is not known in advance.)
The struct is a good idea. Are you attached to the name
I wonder how convenient the arguments Such a function might cover most uses of |
That is exactly the kind of details that I'd prefer to hide, not expose. |
Fair enough. What about |
|
I've pushed my local git repository. All changes were made in March except for the last one. I haven't squashed the commits into one so far because this way it's easier to see what the intent of each change is. This new version would cleanly rebase onto master. The main changes are:
I have to go over the previous again to see if there is any concern that isn't addressed in this version. If would be great if we could wrap this PR up now. |
And that is not exactly helpful for review. I just started reviewing the PR again from the beginning and started writing comments like the above one, before I noticed that the changes I'm commenting on are "outdated". |
Not exactly attached, I just have no idea of a better name right now.
Everybody would be puzzled about what "data" we are talking about. (Maybe unless you clearly document it, but you haven't done that.)
|
Just noticed that you said "I haven't squashed the commits into one"... So, just in case, I'm not suggesting to squash them all into one commit. What I'm suggesting (as always) is: separate commits per separate functional or logical changes (as that makes PRs easier and faster to review), but without changes outdated by later changes in the same PR (as that makes PRs needlessly harder and slower to review). |
|
@matthias314: |
|
OK. I'm busy at the moment. I'll do it soon. |
fa8cfb9 to
9b9004e
Compare
|
I've rebased the PR, hopefully in the way you had in mind. Please have a look at it. |
|
I haven't properly read this thread or tried to review this PR yet, but I think 362e10b isn't like what @dmaluka requested. It might take a while to think and split changes like so on a PR at this size. Some commits months ago may be different, but I think this is how the maintainers organized changes on large PRs:
Although not good examples, I have 2 smaller unrelated experimental branches which are Clipper v2 and
Additionally, I think it would be fine to add small functions like ones under |
|
I am sorry that my comment was unclear on how this PR should exactly proceed. To be clear, I'm only seeing if I can help with anything and decided to join in the review. Since I have a lot more time at the moment, I'm currently looking more into this PR. I think the general idea is good, but first: it doesn't seem like there had been any pushes which more clearly separates the changes in I posted the above comment to implicitly explain differently how the PR should be rebased, and suggest to @matthias314 to try rebasing in such way since it's not easy for others to see the thought process by step anytime. But since I have time now, I am now trying to rebase the PR without any differences to the end result of the modifications. Anyone's rebase could also probably be used as a reference to how @matthias314 (as the author) should make commits here, and not only make review easier. |
|
@niten94 Thanks a lot for looking into this! I found it difficult to combine the existing commits into smaller groups because all those which are now under the umbrella of "modified search and replace methods" had the same goal. Taken alone, any smaller commit did not seem to make much sense. One indication of this is that the commits were interleaved, leading to merge conflicts when moving them around during a rebase. |
|
I meant that the changes can be divided into smaller goals within the code, however I see the difficulty of it now. I've pushed my attempt to rebase at |
|
Sorry for the long delay. I've now rebased the whole PR at my branch and included small documentation changes. Without adding changes outdated later in the PR, I think it's impossible to produce a commit with a clearer diff at the part near @matthias314 could "reset" (not sure if it's the right word) to my branch so that the review can be resumed, right? It's fine to alter it by ways like moving commits around or rewording the messages for example, but hopefully it won't be too hard to work on. |
Just in case, actually the main reason why the review (at least from my side) is currently in hold is just because I'm busy af lately. Sorry for that. I hope I'll get back to it any time soon. |
process `Deltas` in `ExecuteTextEvent` in reverse order added `util.RangeMap` changed `util.isMark` to public `IsMark` added `Loc.IsValid` added `(*Buffer).Expand`
9b9004e to
509e549
Compare
|
I've rebased this PR onto current master. I've also fixed the wrong order of the arguments |
This PR is based on #3575
and therefore a draft at present. It has the following components:RegexpGroupthat combines a regexp with its padded versions as used in match beginning and end of line correctly #3575,DeltasinExecuteTextEventin reverse order. This makesreplacealleasier to implement,LocVoid()andLoc.IsVoid()to deal with unused submatches.The new types and functions are as follows (UPDATED):
The method
FindNextis kept.ReplaceRegexis removed in favor ofReplaceAll. The latter is easier to use in Lua scripts.Currently the simple search functions (
FindDownetc.) take aRegexpGroupas argument to avoid recompiling the regexps. In contrast,FindAll,ReplaceAlland friends take a string argument. Many other variants would be possible. Also, the new search functions ignore theignorecasesetting of the buffer and don't wrap around when they hit the end of the search region. I think they are more useful this way in Lua scripts.You will see that many new internal functions use callback functions. This avoids code duplication. (One has to somehow switch between
(*regexp.Regexp).FindIndex()and(*regexp.Regexp).FindSubmatchIndex()in the innermost function that searches each line of the buffer.)As said before, many details could be modified, but overall I think these functions are very useful for writing scripts. Please let me know what you think.