The modern user interface has come a long way toward fulfilling one of humankind's greatest hopes—to be saved from itself. The undo facility you find in most of today's applications represents the crowning achievement of this pursuit. The next best thing to a time machine, undo allows you to roll back your most recent mistakes—usually with considerable relief—so that you can start making new ones in their place. The automation object model gives you full access to the Visual Studio 2005 undo manager, allowing you to select your own sets of mistakes that can be undone at the click of a mouse.
The basic unit of "undoability" is the undo context. (We'll use this term to mean both an undoable unit—the named entity that appears on the undo list—and the mechanism by which you group individual actions to create an undoable unit.) The Visual Studio 2005 IDE creates undo contexts automatically as you program, allowing you to undo and redo edits to your code. Try the following experiment to see some of the automatic undo contexts created by Visual Studio 2005:
Open a blank text file in Visual Studio 2005.
Type spelled backwards is epyT and press Enter.
Copy a block of text from some document, and paste it into the text file.
When you've finished, click the Undo button's drop-down list and you'll see the list of undo contexts shown in Figure 10-4. (The drop-down list represents the document's undo stack, which is the internal data structure that stores the undoable changes.) The three undo contexts named Paste, Enter, and Type each represent one or more individual actions that can be undone as a whole. You can appreciate the ability to group multiple actions under a single name when it comes to large paste operations, because the alternative would be undoing the pasted characters one by one.
Figure 10-4: A list of undo contexts
An undo context is an atomic transaction; you open the undo context and give it a name, make changes to one or more documents, and then either commit the changes by closing the undo context or abort all the changes. Once committed, the changes can be undone as a group only. You create your own undo contexts by calling methods of the DTE.UndoContext object: Open begins an undo context, SetAborted discards all changes made within the current undo context, and Close commits the changes and pushes the undo context onto the undo stacks of the participating documents.
The undo manager in Visual Studio 2005 allows only one undo context at a time to be open, and to share that undo context, you must follow a few rules. First, always call Open within a try block because this method throws an exception if an undo context is already open. Although you can check the availability of the undo context by using the UndoContext. IsOpen property, which returns True when an undo context is open, a False value won't guarantee that the undo context will still be free by the time your code executes Open. Second, if you open an undo context, you should close it when you're finished with it by calling Close or SetAbort. (Use just one or the other because SetAbort closes the undo context for you, and calling Close on a closed undo context raises an exception.) Third, you should never call SetAbort or Close on someone else's undo context.
Because only one undo context can be open at a time, if you don't acquire the undo context, any changes you make will belong to some other context. If the changes you need to make absolutely must be in their own context, you'll have to poll the UndoContext.IsOpen property until the undo context becomes free.
Sooner or later, when you edit multiple documents within the same undo context, you will run across the problem of desynchronized undo stacks. Suppose you edit Document1 and Document2 within the Link undo context. After you close Link, it gets pushed onto the tops of the two documents' undo stacks. Then, if you undo Link in Document1, you also undo Link in Document2 because their edits belong to the same atomic operation. So far, so good.
Suppose you add some text to Document2. These new edits get pushed onto the top of Document2's undo stack. What happens now when you try to undo Link in Document1? To respect Link's atomicity, you have to undo Link in Document2, and there's the problem—you can't undo Link in Document2 without first undoing the text that was just added. The undo stacks have become desynchronized.
The undo manager solves this synchronization problem by introducing the concept of stack linkage. By default, an undo context that involves more than one document has a nonstrict stack linkage, which allows the atomicity of the undo context to be broken across documents; when the break happens, each document ends up with its own undo context containing only changes to itself. In our previous example, if the Link undo context were created with a nonstrict stack linkage, you could undo Link in Document1 without affecting Document2. Link would disappear from Document1's undo stack but remain on Document2's undo stack, minus the changes to Document1. A strict stack linkage, on the other hand, enforces the undo context's atomicity. If our previous example were to involve a strict stack linkage, the undo manager would cancel any attempt to undo Link in Document1.
You specify whether the stack linkage is strict through the second parameter to UndoContext. Open, passing True for strict. You can identify undo contexts with strict stack linkage by the plus (+) sign that precedes their names on undo lists.
The UndoContexts.StackLinkage macro lets you test the differences between strict and nonstrict stack linkages. This macro creates three documents and adds text to them within an undo context; an optional Boolean parameter controls whether the undo context's stack linkage is strict. Follow these steps to see a nonstrict stack linkage in action:
In the Visual Studio Command window, type Macros.InsideVS.Chapter10. UndoContexts.StackLinkage and press Enter. The macro creates three files—Nonstrict1, Nonstrict2, and Nonstrict3—and adds text to them within the NonstrictLinkage undo context.
In any of the files, click the Undo button and then click the Redo button. You'll see that the changes to the documents are undone and redone as a group.
Add some additional text to the Nonstrict2 file.
Select the Nonstrict3 file, and click its Undo button.
The changes disappear from Nonstrict3 and its Undo button dims. The undo lists for Nonstrict1 and Nonstrict2 still show NonstrictLinkage, however, which means that the atomicity of NonstrictLinkage has been broken. You'll find that Nonstrict1's NonstrictLinkage undoes the changes in Nonstrict1 without affecting Nonstrict2, and vice-versa.
Now, close all the documents and redo the previous steps, but this time add True to the macro command in step 1. The True parameter tells StackLinkage to create files named Strict1, Strict2, and Strict3, and to add text to them within the StrictLinkage undo context. This time, when you try step 4, you'll get the error message "The application cannot undo." That's the essence of strict stack linkage.