Part of my project is to write a text editor that is used for typing some rules, compiling my application and running it. Writing compiler was end and release beta version.
There are basically two good ways to go about it:
the "Command" design pattern
using only OO over immutable objects, where everything is just immutable objects made of immutable objects made themselves of immutable objects (this is less common but wonderfully elegant when done correctly)
The advantage of using OO over immutable objects over the naive command or the naive undo/redo is that you don't need to think much about it: no need to "undo" the effect of an action and no need to "replay" all the commands. All you need is a pointer to a huge list of immutable objects.
Because objects are immutable all the "states" can be incredibly lightweight because you can cache/reuse most objects in any state.
"OO over immutable objects" is a pure jewel. Probably not gonna become mainstream before another 10 years that said ; )
P.S: doing OO over immutable objects also amazingly simplifies concurrent programming.