Prev Up Next
The Document and Graphics Classes Developer's Guide Plugins

The Undo Mechanism

Sketch allows the user to undo every operation that is performed on a document. To achieve this, it needs to keep some information on how to undo the operation. The fact that in Python objects are dynamically typed and functions and (bound) methods are objects allows us to store this information in a very simple and yet very flexible way.

I'll call this information undo information, or undoinfo for short.

Creation and Storage

In Sketch, undoinfo is stored basically in a list in the document object (actually in an instance of the UndoRedo class owned by the document).

A document method that changes the document either creates the undoinfo itself or requires that the functions and methods it calls return the undoinfo. The latter case is the most common, as most operations do not change the state of the document object itself but the state of a layer or a primitive.

In fact, most functions that are expected to return undoinfo do not perform the operation themselves, but call other methods and simply pass the undoinfo they get from those methods back to their caller.

Consider for example the method SetProperties of a group object. A group object has no graphics properties of its own, but setting, e. g., the fill pattern of a group will set the fill pattern of all of the group's children (the objects contained in the group). So its SetProperties method calls the SetProperties method of each of its children and stores the undo info it gets in a list. The undoinfo it returns to its caller will basically say: "to undo the operation you must undo everything in this list".

Representation of Undoinfo

In Sketch, undoinfo is stored as a tuple whose first element is a callable object. The rest of the tuple are the arguments that the callable object must be called with to actually do the undo:

Undoinfo (1)

A tuple t is a tuple of undoinfo of an operation, iff the Python expression `apply(t[0], t[1:])' will undo the operation.

Example: The SetRadius method of the regular polygon plugin:
def SetRadius(self, radius):
    undo = self.SetRadius, self.radius	# create undoinfo
    self.radius = radius	# set the instance variable
    # ... update internal data here ...
    return undo			# finally, return undo info

Undoing SetRadius simply means to set the radius back to the old one, that is, one has to call the method SetRadius of the same object again, only this time with the old radius as parameter. Thus the undoinfo returned is `(self.SetRadius, self.radius)' which is executed before self.radius changes. Note, that in this example the internal data get recomputed automatically during the undo.

Closer examination reveals that performing this undo returns again some undoinfo. This new undo info tells us how to undo the undo! This is