Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [platform-ui-dev] Undo/Redo Proposal






See attched my comments

Dirk
|------------------------+------------------------+------------------------|

|                        |   "Randy               |
|
|                        |   Giffen/OTT/OTI"      |           To:
|
|                        |   <Randy_Giffen@xxxxxxx|
platform-ui-dev@eclip|
|                        |   >                    |   se.org
|
|                        |   Sent by:             |           cc:
|
|                        |   platform-ui-dev-admin|           Subject:
|
|                        |   @eclipse.org         |   [platform-ui-dev]
|
|                        |                        |   Undo/Redo Proposal
|
|                        |   20.11.2001 15:59     |
|
|                        |   Please respond to    |
|
|                        |   platform-ui-dev      |
|
|                        |                        |
|
|------------------------+------------------------+------------------------|













Attached are proposed changes to Undo/Redo to implement a global Undo/Redo
stack for the workbench.





(See attached file: undo_redo2.html)
Title: Undo Redo Proposal

Undo Redo Proposal

Topic: Undo Redo support in the Eclipse workbench
Created: 15 Nov 2001 by Randy Giffen
Modified:  20 Nov 2001 by Randy with comments from Dirk

<db>
Comments made by Dirk_Baeumer@xxxxxxx
</db>

Summary

Currently, the Eclipse workbench includes global actions for Undo and Redo. They appear as the first two items in the edit menu. As global actions, they are targeted to the current active part in the current perspective. If the active part does not supply a global action handler for them, they are disabled. This means that the action that will be performed by Undo or Redo is dependent on the active part and thus the behavior of these actions is modal. In many cases the user must go back to the appropriate part before being able to undo an action performed there.

The Details

Problem

Global actions are actions defined by the Eclipse workbench UI which delegate their enablement and action to a global action handler supplied but the active workbench part. Global actions were introduced to provide a degree of consistency in the UI. The idea was that common functions like undo/redo, cut/copy/paste, and delete should have a consistent location in the UI and consistent menu accelerators. An additional benefit is that views (which cannot contribute to the window menu and thus cannot define menu accelerators) are able to respond to the menu accelerators defined for global actions.

Thus, in a way, the name global is a bit misleading in the undo/redo case. The actions do not support a global undo/redo stack. They support a targeted undo/redo directed at, and implemented by, the active part. This is typically not what most users expect. In the case of an IDE, they are using the UI to manipulate (make changes to) a model. They expect that undo/redo will allow them to back out of or redo these changes regardless of what part of the ui they are currently working.

This architecture requires that parts maintain their own undo/redo stack (although theoretically some parts may decide to implement a shred stack among themselves).

Another problem is that actions that don't belong to a view (e.g. like the "Refactor" menu which gets retargeted to the active view) can't access the undo/redo actions in the edit menu.

Possible Options

Let us assume for the moment that a workbench undo/redo stack is a good idea and consider its implementation (this is based on discussions and PR 1887). We would define an IUndoManager and add a method to IWorkbench to obtain such a manger. The manager would have api for adding and removing a IUndoActions from the manager.
IUndoAction would have the following definition

public interface IUndoAction {
 String getLabel();
 void undo();
 void redo();
 boolean isUndoValid();
 boolean isRedoValid();
 void dispose();
}

Comment: Should we consider having an abstract UndoAction rather than an IUndoAction? In some cases this would allow us to modify API in a non-breaking way but it means that clients will not be able to use an existing type to implement IUndoAction (probably a rare case).

The manager will ensure that the top undo action and top redo action are valid by calling the isUndoValid() and isRedoValid() api methods. If the undo/redo action is no longer valid it will be punted and the next one moved to the top.

The valid check is required as an undo action cannot validate itself simply by listening to model changes. It would also somehow need additional information if the received delta will have an undo which will be put onto the undo stack. Consider the following case: A Java refactoring renames file A.java to B.java. Then the user goes to the navigator and renames B.java to C.txt. If the navigator pushes an undo action onto the stack, the refactoring undo is still valid since the user must first perform the undo for "rename B.java to C.txt" before he can execute the undo for "rename A.java to B.java". If the refactoring kernel listened to model changes only, without knowing that there is an undo for the renaming of B.java to C.txt, the kernel would invalidate the undo for rename A.java to B.java.

Note that we are only going to validate the top undo/redo actions. Validating others on the stack would be too complicated. Consider the case where one renames a compilation unit. In this case we have two changes, one that renames the top level type and one that actual renames the file. To be able to figure out if the undo for this refactoring is still valid, we have to check if both changes are still valid. To do so we have to simulate the undo of the rename of the file to be able to check if we still can undo the renaming of the top level type. This is necessary since the rename of the top level type doesn't know anything about the rename of the file. For example:

Rename A.java to B.java.

  1. change top level type in file A.java to B. The undo is change top level type in file A.java to A
  2. change filename from A.java to B.java. The undo is change B.java to A.java.
To figure out if this refactoring can still be undone we have to simulate the rename form B.java to A.java. Otherwise we can't check the undo "change top level type in file A.java to A" since A.java doesn't exist.

We would add tool bar buttons for undo and redo with a menu button to the right like the "new" button. The menu button would show the current undo or redo stack and let you select at any level in the stack. Again it is important that IUndoActions be able to validate themselves since this will determine the enablement of these buttons. The undo and redo actions in the Edit menu would show the label for the action currently at the top of their stacks.

Once the new api is available, it is likely than many more actions would become undoable (ex. resource rename, move etc.).

<db>
I somehow have the feeling that my above comments (mostly examples from refactoring) are not easy to understand. I therefore will try to explain my problems a little bit more detailed:

As said there are two approaches to figure out if an undo action is still valid.

  1. Polling: the undo manager asked the undo action if it is still valid before it tries to execute it.
  2. Listening: an action informs the undo manager about the fact that it is no longer undoable and the manager flushes the stack. If the undo stack only contains model changes then it is very unlikely that any other undo action on that stack is still undoable.

The question is now, how does an undo action figure out that it is still valid. An undo action could either listen to model changes or the actions simulates the undo when isUndoValid is called. Simulating the undo puts all the coding onto clients that provide undo actions. Especially in refactoring simulating an undo to figure out if an undo action is still valid can be very compilcated. Consider the example from above, were we rename a compilation unit A.java to B.java. In this case we have one undo action but two elementary changes: renaming the top level type in A.java from A to B and then renaming the compilation unit from A.java to B.java. The corresponding undo action would do the following: (1) rename B.java to A.java and (2) then change the top level type in A.java from B to A. To actual simulate the second elementary change we have to perform the first change (or at least do some tricky in memory simulation) otherwise file A.java doesn't exist.

Instead of simulating the undo the undo action can listen to model changes to figure out if it is still valid. Listening to model deltas has two problems: 

  1. an undo action must know if the received delta has an undo. If so it can ignore the delta since there will be an undo action that puts the model into its previous state. Otherwise a delta created by simple rename like B.java to C.txt would inavlidate the above rename type refactoring undo even if there is an undo for "rename B.java to C.txt".
  2. since the platform batches "operations" a delta can't be assigned to one "operation". Consider the following case: there is an operation A that deletes file D.java. Inside that operation a refactoring is called that renames A.java to B.java. After the refactoring is executed it pushes an undo action onto the stack. Since the refactoring is enclosed by a second operation the delta is generated after file D.java is deleted. How does the undo action for the refactoring know that the delta is partly created by itself ? Otherwise it may invalidate itself based on a delta that was produced by itself.

</db>

Problems with a Global Undo/Redo Stack

1) Since undo and redo are part of the IWorkbenchActionConstants.GLOBAL_ACTIONS, it would be a breaking API change to no longer support undo and redo as global (retargetable) actions. Parts that are currently maintaining their own undo/redo stack would have to change to add IUndoActions to the workbench stack. Until then the Edit menu actions and their accelerators would no longer trigger an action in the part. This is probably an acceptable breaking change.

2) The text widget implements its own undo. For example create a new task and edit its description note that there is an undo item in the widgets popup menu. This undo is local to the widget. Currently Undo is not enabled under edit in this case since the tasklist does not supply a global action handler for this purpose (There is no api on Text to allow programmatic undo). However if we have a global stack it is likely that Edit>Undo would be enabled but it would perform something different than undo on the popup.
Thus should we:
    1) We could ask SWT for undo API on text in this case.
    2) Continue to allow Undo/Redo to be global actions for which we simply define a default behavior (which will not be retargeted by 95% of the parts).

3) It is not clear that an IDE built using the workbench has only a single model. For example, suppose I
i) Edit some .java file in the Java perspective
ii) Switch to the Team perspective and create something in the Repositories view
iii) Switch back to the Java perspective, decide I don't like my change and press Undo.
A global stack will cause the item I created in the team perspective to be removed (this will perhaps happen silently).
But this is exactly what some would expect. They find it confusing to have more than one undo stack in an application.

4) How do we define what can go on the undo redo stack? Do we limit it to model changes? For example opening an editor could have an undo that would allow the user to close the editor and return to the previously active part.
 


Back to the top