Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I'm implementing a Java app that includes an Undo/Redo stack. I've noticed that some apps (such as TextEdit on Mac OS X) let you choose "Undo Typing" from the Edit Menu after typing some text. I'd like to implement that sort of thing into my app as well, but I'm having a really hard time finding guidelines about how it should behave.

With some trial and error, my best guess about how TextEdit's Undo Typing behaves is:

  • When the user types a new character (or types the delete key), merge it into the previous Undo Typing item if one is at the top of the Undo stack, unless one of the following situations occurs
  • Always create a new Undo Typing item after the user continues to type after at least 15 seconds of inactivity
  • Always create a new Undo Typing item after the user is typing for an extended period of time and some condition is met (couldn't figure out if this was time based or character count based).
  • Always create a new Undo Typing item when any text is selected and then deleted or overwritten (selecting text, not making a change, then returning to the original insertion point and continuing to type does not trigger this)

In practice, Apple's strategy seems to work (at least it works for me when I type), but as noted by the last point, I haven't really been able to figure out the rules. Also, it seems like other programs follow different rules, such as Microsoft Word. Google has not turned up a defined list of rules for any implementation of Undo Typing and I haven't come across any best practices for how it should behave. So how should it behave? Or is it just up to the whims of the programmer?

EDIT: Just to clarify, I'm not interested in implementation details right now. I'm especially curious as to whether or not an authoritative reference (e.g. best practices or user interface document) exists describing this or a description of how it is implemented across multiple products.

share|improve this question
    
My suggestion: compress the modifications to the point where it is still possible to reconstruct the exact sequence of pressed keys from the undo information, and no more. For example, this means that if the user types something and immediately uses backspace to delete it, there should be an undo point in between. –  Ambroz Bizjak Feb 27 '13 at 4:01
    
Maybe you could also add a new "Undo Typing item" every time the user create a new line and maybe every time the space bar is used immediately after characters input. IMO 15 second of waiting time before a new "Undo Typing item" could be a little long but that's just me. (I would go for around 5 seconds) –  user82529 Feb 27 '13 at 4:01
    
Or maybe "exact sequence of pressed keys" should be relaxed to "the state of the text after each modification". The idea is to prevent any text from getting lost due to compression. –  Ambroz Bizjak Feb 27 '13 at 4:05
    
It looks to me like TextEdit merges spaces and deletes in with the last Undo Typing item, provided the other conditions aren't met. So 124<delete>3, then undoing and redoing it results in 123. I guess the advantage of this is that it results in the user's final state of the text, somewhat like the above suggestion. –  Thunderforge Feb 27 '13 at 4:07
    
Have you tried searching Patents yet? (The rules are usually encoded in the library layer, not exposed to user code.) –  Donal Fellows Feb 27 '13 at 5:50
show 3 more comments

2 Answers

up vote 5 down vote accepted

If you're looking for an authoritative source, I think the best Mac-related material will be found in the Undo Architecture document from Apple.

I don't think you're going to find a list of rules about when you should or shouldn't coalesce undo events, though. What feels right for one application won't necessarily make sense for another one. For example, coalescing keystrokes makes sense in a text editor because the user will probably see typing a paragraph as a single action and not as 539 separate actions, and also because you don't want the user to have to undo 539 times just to get to the point they were at before they typed that paragraph. But what about move operations on a shape in a drawing program? Or sequential adjustments to a fill color? You could make a good case for these being coalesced or not, depending on the nature of your program.

Always create a new Undo Typing item after the user is typing for an extended period of time and some condition is met (couldn't figure out if this was time based or character count based).

It's based on autosave. Lucky for you, the TextEdit source code is available and well commented. I think that if you take a look at it, you'll get a better idea of what's going on and why. For example:

- (void)saveToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation completionHandler:(void (^)(NSError *error))handler {
    // Note that we do the breakUndoCoalescing call even during autosave, which 
    // means the user's undo of long typing will take them back to the last spot an 
    // autosave occured. This might seem confusing, and a more elaborate solution may 
    // be possible (cause an autosave without having to breakUndoCoalescing), but since 
    // this change is coming late in Leopard, we decided to go with the lower risk fix.
    [[self windowControllers] makeObjectsPerformSelector:@selector(breakUndoCoalescing)];
 ...

I know you said you're not interested in implementation details yet, but looking at the way that Apple implemented TextEdit can inform the decisions you make for your own application.

share|improve this answer
1  
I think that I'll declare this the best answer. I appreciate you giving the link to Apple's implementation as well some code for the specific example that I provided. I think you're right though, it generally is based on the needs of the application and nobody has really made an effort to standardize what those needs are and how to address them. –  Thunderforge Feb 27 '13 at 16:38
add comment

on keydown -> timer representing your idle starts

on keydown/timer-running -> reset timer

on keydown/no timer-running -> readjust cell blocks to prepare for a new preserved state as position changes

idle-timer runs down -> Establish a new undo state

I wouldn't track keypress identities. I'd split into cellular blocks of text (by character count) that allow you to track position by offsets from the nearest cell's beginning positions so you don't have to save the entire state of a tolstoy novel every time an idle timer runs down. Readjusting those offsets when cells before other cells get edited is the tricky part.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.