josephg / ShareJS

Collaborative editing in any app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Improved Undo for 0.7

dignifiedquire opened this issue · comments

I was reading up on undo strategies in OT environments and was wondering if it made sense and it's possible to implement some of the algorithms for undo in Undo Any Operation at Any Time in Group Editors into ShareJS.

Yet another reason to keep the OT types invertible @josephg 😄

Wout.

On Oct 2, 2013, at 15:16 , Friedel Ziegelmayer notifications@github.com wrote:

I was reading up on undo strategies in OT environments and was wondering if it made sense and it's possible to implement some of the algorithms for undo in Undo Any Operation at Any Time in Group Editors into ShareJS.


Reply to this email directly or view it on GitHub.

:p

We can implement undo using the invert function that exists at the moment, though I'm not sure how long we should keep history. The context structure we have now should would make sending undo operations to the textarea or whatever make a lot more sense. I'd feel bad about adding more crap into the doc class but there's so much crap in there already that like, whatever. I lost that fight months ago and ... in for a penny, in for a pound I guess.

As for the invertability thing - we don't need invertability to get a decent undo system going. The OT types could just have a method for 'semantic invert' or something:

type.semanticInvert = (doc, op) -> inv_op such that apply(apply(doc, op), inv_op) is semantically equivalent to doc.

For types with an invert function anyway, semanticInvert would just call invert on the operation. For types without an invert function (eg, a text type), it would simply look at the deleted / modified stuff in the doc and make an op that puts it back. This would play well with both OT-aware undo and any fancy TP2 OT types I might want to add to ShareJS down the line.

Ok I suppose that makes sense. I'll need to read that paper thoroughly to see if semantic invert also leads to proper group undo.

One of the things that I keep wondering about is how Share should handle large conflicting changes when a long-offline client comes back, especially for text types. In 0.6 there is no conflict possible, you will just get gibberish from the transformed ops. Having a good undo system will help handle that at least...

Does 0.7 have the concept of conflicts? I haven't had time to look at the code yet :-( but since some types don't have invertability I suppose so?

Wout.

On Oct 7, 2013, at 12:53 , Joseph Gentle notifications@github.com wrote:

:p

We can implement undo using the invert function that exists at the moment, though I'm not sure how long we should keep history. The context structure we have now should would make sending undo operations to the textarea or whatever make a lot more sense. I'd feel bad about adding more crap into the doc class but there's so much crap in there already that like, whatever. I lost that fight months ago and ... in for a penny, in for a pound I guess.

As for the invertability thing - we don't need invertability to get a decent undo system going. The OT types could just have a method for 'semantic invert' or something:

type.semanticInvert = (doc, op) -> inv_op such that apply(apply(doc, op), inv_op) is semantically equivalent to doc.

For types with an invert function anyway, semanticInvert would just call invert on the operation. For types without an invert function (eg, a text type), it would simply look at the deleted / modified stuff in the doc and make an op that puts it back. This would play well with both OT-aware undo and any fancy TP2 OT types I might want to add to ShareJS down the line.


Reply to this email directly or view it on GitHub.

From what I understand this would be the basic structure for implementing this into ShareJS

  • Add semanticInvert to the ottypes.
  • Implement the undo algorithm using semanticInvert into the document, exposing something like doc.undo({scope: 'local', type: 'single-step'})

Am I right in saying all this code can live in the client? Or should it be moved onto the server?
@josephg When you are talking about semanticInvert for the new text type do you mean something like this ?

var doc = type.create();
doc.apply([' world', 0]);
var op = 'hello';
doc.apply(op)
var inverseOp = doc.semanticInvert(op)
// inverseOp ==  [0, {d: 5}]
doc.apply(inverseOp)
// doc == ' world' with cursor at position 0
  • We can probably just call invert for types that don't have a semanticInvert function. There's already enough functions in ottypes, and that saves writing boilerplate code in the invertable types like JSON.
  • Yes, the whole thing can be done client-side. What is type: 'single-step' supposed to do? The simplest API could be doc.undo(). That said, it might make sense to pass in a context as well. If context is set, we can copy submitOp()'s behaviour and not notify the context about the undo event. (This will allow us to use native undo in browsers).

You don't need extra zeros at the start and end of your ops, and you're conflating the OT type and the document class, but otherwise yeah - thats exactly what I mean. It would be like this:

var str = type.create();
str = type.apply(str, [' world']); // str is ' world'
var op = ['hello'];
str = type.apply(str, op); // str is 'hello world'
var inverseOp = type.semanticInvert(str, op) // inverseOp is [{d: 5}]
str = type.apply(str, inverseOp); // str is ' world' again

We need to pass in the current document snapshot to semanticInvert because you might be undoing a delete and the delete op might not know which characters need to be inserted.

@josephg so I've started implementing semanticInvert for the text type and most if it is straight forward, but I'm not clear on the process of inverting a delete operation. You say we need to pass in the documents current snapshot, but in that we don't have the information about what characters were deleted, so we would need to pass in the snapshot before deletion? Or am I missing something?

commented

@josephg The Undo Any Operation at Any Time in Group Editors algorithm have exponential complexity both in do and undo transformation, and ET function is not easy to write when operations are complex.
For one-dimensional system, I think this article 'An Algorithm for Selective Undo of Any Operation in
Collaborative Applications' fits more.