-
Notifications
You must be signed in to change notification settings - Fork 237
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
Refactor: ChangeType in TextChange #486
Comments
|
Thanks for the ideas. However, neither makes much sense to me at first glance. And if one needs to explain it, that's also problematic. As I thought about this today, I thought the nature of the data is the type of change stored. However, that data is used is to determine whether a merge should occur. So I thought, why not call it |
@kuc Before merging 487, I'd be curious to hear your thoughts on the naming. |
@JordanMartinez OK, I will look on it in a free moment... |
@TomasMikula #487 should resolve any known and unknown regressions introduced by #458. Could you make a new milestone release? |
The choice that only two insertions or only two deletions will be merged is somewhat arbitrary. We should avoid making arbitrary choices, at least in the core. I understand that this is in an attempt to provide more reasonable default behavior. This should be possible to do on top of the universal core, though. Specifically, limiting merges by type of change can be implemented in the function passed to undo manager constructor (it doesn't have to just directly invoke Aside, I'm skeptical that |
In other words, #322 is (in some ways) a symptom of the developer not having complete control over how the undo/redo works. Thus, the approach of #402 / #458 is incorrect and something like #333 would need to be implemented (meaning, giving the developer more control as to how undo/redo works).
It wasn't supposed to be of much use, just provide the bare minimum functionality to fix #322 and fix any regressions introduced by #458 so that a milestone release could be made that has all the new functionality since |
True, but this was
But if they do use the original |
#322 has to be fixed independently of what merging strategy is implemented. We shouldn't get an exception with any strategy. I see a couple of ways to fix #322. Solution ANotice that with the current arguments passed to <C> UndoManager create(
EventStream<C> changeStream,
Function<? super C, ? extends C> invert,
Consumer<C> apply,
BiFunction<C, C, Optional<C>> merge); the UndoManager has no way of knowing whether the result of For RichTextFX to work properly with this behavior of UndoFX, the only possibility is to accept edits which are no-ops and emit corresponding empty changes. This solution provides the most guarantees to clients (such as the UndoManager): every edit will result in a change event. Solution B (requires UndoFX changes)Another option is to give the UndoManager a way to know whether Note also that the two solutions can coexist; we could do both. I would also like to mention that the set of changes resembles a group. (We don't quite have a group, because not any two changes can be merged.) What this means is that
These are useful properties to keep in mind when implementing merge. |
While I get the idea behind Solution A, accepting and emitting empty changes seems silly; an empty change, by its definition, isn't a change at all, just wasted memory/time. Isn't it just better to return immediately on calls of At the very least, Solution B should be implemented because this issue will arise in any project that uses UndoFX (whether that be RTFX or something else). |
When someone's calling But yeah, to give us more flexibility, I would leave it undefined whether empty changes will be emitted. Then users cannot count on empty changes being emitted, neither can they count on any emitted change being non-empty.
Not if there is a notion of an empty change and empty changes are being silenced. But I agree it should be implemented anyway. |
Right, which is why the
"Allocate budget?" Not sure what you mean by that. Regardless, doesn't the "return immediately on empty document" approach deal with anyway?
What do you mean by "empty changes are being silenced?" You seem to be implying that there are projects that might have an empty change (the first half of your statement). But it's the second half that leaves me puzzled. |
When someone's calling replace(n, n, /* empty document */), it is likely
because they didn't care to check whether the document is empty (it will
not be empty in general, and they don't care for the few cases when it is).
Right, which is why the replace method can check if the document is empty
and return immediately if it is. The user does not have to worry about that.
If they are going to do N replace operations, some of which potentially
empty, they typically have to "allocate budget" for N full replace
operations anyway.
"Allocate budget?" Not sure what you mean by that. Regardless, doesn't the
"return immediately on empty document" approach deal with anyway?
CPU budget. That they have to account for the worst case: none of the
operations being no-op.
Not if there is a notion of an empty change and empty changes are being
silenced.
What do you mean by "empty changes are being silenced?" You seem to be
implying that there are projects that might have an empty change (the first
half of your statement). But it's the second half that leaves me puzzled.
It should have been "Only if" instead of "Not if". By being silenced I mean
not emitted.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#486 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAadH9XeItv25GkEbpTJyoXYTQtaZdbcks5rvZeEgaJpZM4M5OK_>
.
|
Oh ok.
Ok, I thought that's what you meant. Then, couldn't one return Predicate<Change> isNoOpChange = c -> false; |
Ah, my email reply didn't get formatted well. But you figured out what was quoted and what was new, anyway :)
Yes, but the discussion was about the current UndoFX. You said
and I said (well, meant to say) that only if that project has a notion of empty changes and silences them. |
Yeah... I was a bit confused about the formatting at first 😄
Ah, yeah, I was talking about the updated version of UndoFX |
I don't have the time right now to re-read through this issue, but right now |
Released UndoFX 1.3.0 with the required feature. |
Referring to your comment, I still think Solution A is silly, but I agree that its necessary in case others want to implement their own merge method. So, we'll need to remove the However, one question arises. In a Before I forget, I should reopen this issue. |
Not if they also provide a compatible
Is there a problem with defining identity change as a change where the inserted document equals the removed document? |
Ah, that would be it. |
Now that #495 is merged, should we now revert back to the previous merge method? Or is that the new issue to tackle? |
I think that at the very least I realize I had a bad memory of the previous implementation when I said
It didn't merge whenever possible, but only when the next change continued at the place where the previous one finished. I think this was a somewhat reasonable default, and did not suffer from #493. As a matter of principle, I still think providing the most extreme choice is a good idea, but doesn't need to be addressed now. In the future, we might want to support non-contiguous changes, so don't spend too much time on something that might be replaced anyway. |
Agreed.
So, what I'm reading is we should revert back to the original merge method now and figure out a more extreme choice later on. Those who don't want to use that method can implement their own.
How would that work? |
How: The representation of a change would basically be a list of things we today call a change. Why: Ability to represent global find/replace as a single undo item. |
I assumed such would be the case when #222 gets implemented ( |
Closed by #496 |
To address #322, I reimplemented the solution in #402 by using more readable code via
ChangeType
, which was through lazy evaluation initially. This caused regressions, as discovered in #472, and was later evaluated immediately in #470 to help find these regressions. Besides simplifying the logic, I usedChangeType
because I thought developers would be able to use it in their code to quickly determine what kind of change the change was. By doing this, I realize now that I was solving the first problem and indirectly trying to solve another problem (adding convenience methods toTextChange
that allows one to quickly determine what kind of change it was).And that is the issue: my solution to the second problem was not fully thought out because it was not the issue I was trying to solve. I didn't realize it then (but do realize it now because of the regressions, both known and yet unknown, that have appeared since
0.7-M3
) that aRichTextChange
can be a number of different changes (insertion, deletion, replacement, text style change, or a paragraph style change)--change tyes whichChangeType
does not cover.So I see two options for addressing this bug:
TextChange
that can get exactly what kind of change type it isTextChange
is an insertion or deletion type of change and thus whether to merge it with anotherTextChange
.I'm against the former because it will be more invasive to the current API and using a
TextChange
does not always require knowing what kind of change it is. For example, sometimes one just needs to know how much to adjust the caret based on how much content was removed and inserted.I think the latter is best because it will immediately address all possible (known and currently unknown) regressions related to #458.
To still maintain code readability, I don't think we should just implement methods like
TextChange#isInsertion
andTextChange#isDeletion
. Rather, I thinkChangeType
(which I think needs to be better named since it doesn't address all the changes aTextChange
could be and since it pollutes the namespace that developers might want to use later in their own code) should be as follows:And then the
TextChange#mergeWith
code would still read:Unfortunately, I can't think of a better name to address the namespace pollution issue I raised above. Does anyone else have a better name?
However, #472 did show that sometimes RTFX is inefficient because it tries to make an empty replacement. I wonder if there are others areas that could be further optimized so that
replace
always replaces something and ignores calls that ultimately do nothing. For example, ignoring "style change" calls that don't actually restyle the content because that content already has that style.The text was updated successfully, but these errors were encountered: