Generalized Undo Support in Eclipse

Last updated:  Dec. 17, 2004

Status:  Proposal

(see also 37716)

Problem Description

In R3.0.1 of the Eclipse SDK, there is no generalized support for undoing user actions.  Each plug-in is left to implement its own strategy for undo, if at all.  This approach can cause problems for both Eclipse users and plug-in developers:

 

 

Further, the meaning of undo can be very different depending on what the user is doing.  The most common type of undo is text undo.  The user can undo lightweight edit operations such as inserting, replacing, or deleting text.  Other types of undo may involve more complex, heavyweight operations that affect an underlying model of many elements.  For example, the JDT refactoring support provides undo support for refactoring operations that involve many Java elements at different levels, such as Java packages, compilation units, classes, and methods.

Operations Framework

The proposed framework defines an interface for describing work, called an “operation” (IOperation), that can be executed, undone, and redone.  Operations are created, executed, and added to an operations history (IOperationHistory).  Operations which are comprised of distinct steps are represented as compound operations.  Compound operations must be executed, undone, or redone as a unit, and can never be partially undone. 

 

Operations can be assigned one or more contexts (IOperationContext) to which they apply.  An operation  context describes the context in which the user is working when an operation is performed, undone, or redone.  Contexts can be used by workbench parts to filter the operations history, so that only those operations that have been assigned the part’s context are available for undo/redo when that part is active.

 

The interface for operation contexts is left very general so that operation implementers may choose the appropriate representation for their contexts.  In some cases, a part-oriented context may be appropriate. For example, a text editor’s context is related to the editor itself and its life-cycle is similar to that of the editor.  The navigator’s context is closely related to the workspace model objects, and the context related to the workspace has a life cycle to similar to that of the workspace itself.

 

Contexts can be assigned to operations in multiple ways:

 

1.       The context can be assigned initially when the operation is created.  For example, text operations are triggered by typing in the editor, and the editor can assign its context to the operation before adding it to the history. 

2.       A listener interface on the operations history allows listeners to detect when an operation is added to the history.  Listeners can decide if their context should be added to the operation. 

 

An operation may have more than one context.  For example, an operation may affect many elements in the workspace, including one that is currently open in an editor.  That operation could be assigned two independent contexts, so that it can be undone from both the editor and the view manipulating the model.  

 

Levels of Integration and Migration

Many plug-ins have already invested heavily in building objects that describe undoable user commands or operations.  It is not expected that all plug-ins will adopt the framework completely, given the individual schedules and other constraints for each plug-in.  However, the integration can be achieved in phases that will immediately provide value to dependent plug-ins.  The following adoption strategy is strongly encouraged:

 

  1. Existing command/undo frameworks can implement the IOperation interface on their existing command objects, while still maintaining their individual strategies for managing undo stacks or histories.  If there is a substantial investment in an existing model-based operation or command framework, wrappers could be used to map IOperation protocol to the existing protocol.  The commands/operations need not be assigned a context or added to a common operation history.  This level of integration allows command hierarchies built in different frameworks to be treated the same by plug-ins that depend upon these different frameworks.  Clients of existing command frameworks may then use the workbench operation history, assign contexts to operations as needed, and even add operations to the history, while still using commands built on earlier frameworks.
  2. Existing command/undo frameworks may use the listener interfaces provided by the operations history to listen for operations that are of interest.  These commands may be wrapped or otherwise recorded so that they can be undone from privately maintained undo implementations.  This level of integration allows views and editors to appear more tightly integrated with the operations framework, since workbench operations of interest can be undone or redone from private undo implementations.
  3. Full integration is achieved by using the workbench operation history to record the undo and redo history as operations occur.  Once all plug-ins share an operation history, clients will be able to use unified listeners and handlers to track execution of operations, undo, and redo them. 

 

Framework interfaces will be defined in the org.eclipse.core.operations package.  This package will be contained in a plug-in that has no dependencies on the Eclipse runtime, so that raw JFace users may make use of it.  (The exact plug-in containing the framework package is not defined at this time, as these packages may be combined with other packages that provide similar headless infrastructure.)

Framework interfaces

IOperation

IOperation defines an operation that can be executed, undone, and redone.  Operations typically have fully defined parameters. That is, they are usually created after the user has been queried for any input needed to define the operation.

 

Operations determine their ability to execute, undo, or redo according to the current state of the application. They do not make decisions about their validity based on where they occur in the operation history. That is left to the particular operation history.

 

public void addContext(IOperationContext context)

Add the specified context to the operation. If the context is already present, do not add it again.

Parameters:

context - - the context to be added

public void removeContext(IOperationContext context)

Remove the specified context from the operation. This method has no effect if the context is not present.

Parameters:

context - - the context to be removed

public boolean hasContext(IOperationContext context)

Returns whether the operation has the specified context.

Parameters:

context - - the context in question

Returns:

true if the context is present, false if it is not.

public IOperationContext [] getContexts()

Returns the array of contexts that have been assigned to the operation.

Returns:

the array of contexts

public boolean canExecute()

Returns whether the operation can be executed in its current state.

Returns:

true if the operation can be executed; false otherwise.

public boolean canRedo()

Returns whether the operation can be redone in its current state.

Returns:

true if the operation can be redone; false otherwise.

public boolean canUndo()

Returns whether the operation can be undone in its current state.

Returns:

true if the operation can be undone; false otherwise.

public org.eclipse.core.runtime.IStatus execute(org.eclipse.core.runtime.IProgressMonitor monitor)

Execute the operation. This method should only be called the first time that an operation is executed.

Parameters:

monitor -

Returns:

the IStatus of the execution. The status severity should be set to OK if the operation was successful, and ERROR if it was not. Any other status is assumed to represent an incompletion of the execution.

public org.eclipse.core.runtime.IStatus redo(org.eclipse.core.runtime.IProgressMonitor monitor)

Redo the operation. This method should only be called after an operation has been undone.

Parameters:

monitor -

Returns:

the IStatus of the redo. The status severity should be set to OK if the redo was successful, and ERROR if it was not. Any other status is assumed to represent an incompletion of the redo.

public org.eclipse.core.runtime.IStatus undo(org.eclipse.core.runtime.IProgressMonitor monitor)

Undo the operation. This method should only be called after an operation has been executed.

Parameters:

monitor -

Returns:

the IStatus of the undo. The status severity should be set to OK if the redo was successful, and ERROR if it was not. Any other status is assumed to represent an incompletion of the undo.

public void dispose()

Dispose of the operation. This method is used when the operation is no longer kept in the history. Implementers of this method typically unregister any listeners.

public java.lang.String getLabel()

Return the label that should be used to show the name of the operation to the user. This label is typically appended to the "Undo" or "Redo" menu entry.

Returns:

the label

public java.lang.String getDescription()

Return the description that should be used to further describe this operation to the user. The description is used in history lists when the user requests more information about an operation.

Returns:

the description

IOperationHistory

IOperationHistory tracks a history of operations that can be undone or redone. Operations are added to the history once they have been initially executed. Clients may choose whether to have the operations history perform the initial execution or simply add the operation to the history. Once operations are added to the history, the methods canRedo() and canUndo() are used to determine whether there is an operation available for undo and redo in a given operation context.  The context-based protocol implies that there is only one operation that can be undone or redone at a given time in a given context. This is typical of a linear undo model, when only the most recently executed operation is available for undo. When this protocol is used, a linear model is enforced by the history. It is up to clients to determine how to maintain a history that is invalid or stale. For example, when the most recent operation for a context cannot be performed, clients may wish to flush the history for that context.

 

Additional protocol allows direct undo and redo of a specified operation, regardless of its position in the history. When a more flexible undo model is supported, these methods can be implemented to undo and redo directly specified operations. If an implementer of IOperationHistory does not allow direct undo and redo, these methods can return a status indicating that it is not allowed.

 

Listeners (IOperationHistoryListener) can listen for notifications about changes in the history (operations added or removed), and for notification before and after any operation is executed, undone or redone. Notification of operation execution only occurs when clients direct the history to execute the operation. If the operation is added after it is executed, there can be no notification of its execution.

 

IOperationApprover defines an interface for approving an undo or redo before it occurs. This is useful for injecting policy-decisions into the model - whether direct undo and redo are supported, or warning the user about certain kinds of operations. It can also be used when objects have state related to the operation and need to determine whether an undo or redo will cause any conflicts with their local state.

 

public void add(IOperation operation)

Add the specified operation to the history without executing it. The operation should have already been executed by the time it is added to the history. Listeners will be notified that the operation was added to the history.

Parameters:

operation - - the operation to be added to the history

public org.eclipse.core.runtime.IStatus execute(IOperation operation,
                                                org.eclipse.core.runtime.IProgressMonitor monitor)

Execute the specified operation and add it to the operations history if successful. This method is used by clients who wish operation history listeners to receive notifications before and after the execution of the operation. Listeners will be notified before ( aboutToExecute) and after (done or operationNotOK). If the operation successfully executes, an additional notification that the operation has been added to the history (operationAdded) will be sent.

Parameters:

operation - - the operation to be executed and then added to the history

Returns:

the IStatus indicating whether the execution succeeded. The severity code in the returned status describes whether the operation succeeded and whether it was added to the history. OK severity indicates that the execute operation was successful and that the operation has been added to the history. Listeners will receive the done notification. CANCEL severity indicates that the user cancelled the operation and that the operation was not added to the history. ERROR severity indicates that the operation did not successfully execute and that it was not added to the history. Any other severity code is not specifically interpreted by the history, and the operation will not be added to the history. For all severities other than OK, listeners will receive the operationNotOK notification instead of the done notification.

public void remove(IOperation operation)

Remove the specified operation from the history. Listeners will be notified of the removal of the operation. This method is used by clients who want to flush a particular subset of the history.

Parameters:

operation - - the operation to be removed from the history

public org.eclipse.core.runtime.IStatus redo(IOperationContext context,
                                             org.eclipse.core.runtime.IProgressMonitor monitor)

Redo the most recently undone operation in the given context

Parameters:

context - - the context to be redone, or null if the context does not matter

monitor - - the progress monitor to be used for the redo, or null if no progress monitor is provided.

Returns:

the IStatus indicating whether the redo succeeded. The severity code in the returned status describes whether the operation succeeded and whether it remains in the history. OK severity indicates that the redo operation was successful and that the operation has been placed on the undo history. Listeners will receive the redone notification. CANCEL severity indicates that the user cancelled the operation and that the operation remains in the redo history. ERROR severity indicates that the operation could not successfully be redone and that it has been removed from the history. Listeners will also be notified that the operation was removed. Any other severity code is not specifically interpreted by the history, and is simply passed back to the caller. For all severities other than OK, listeners will receive the operationNotOK notification instead of the redone notification.

public org.eclipse.core.runtime.IStatus redoOperation(IOperation operation,
                                                      org.eclipse.core.runtime.IProgressMonitor monitor)

Redo the specified operation

Parameters:

operation - - the operation to be redone

monitor - - the progress monitor to be used for the redo, or null if no progress monitor is provided

Returns:

the IStatus indicating whether the redo succeeded. The severity code in the returned status describes whether the operation succeeded and whether it remains in the history. OK severity indicates that the redo operation was successful and that the operation has been placed on the undo history. Listeners will receive the redone notification. CANCEL severity indicates that the user cancelled the operation and that the operation remains in the redo history. ERROR severity indicates that the operation could not successfully be redone. The operation will remain at its current location in the history, and callers must explicitly remove it if desired. Any other severity code is not interpreted by the history, and is simply passed back to the caller. For all severities other than OK, listeners will receive the operationNotOK notification instead of the redone notification.

public org.eclipse.core.runtime.IStatus undo(IOperationContext  context,
                                             org.eclipse.core.runtime.IProgressMonitor monitor)

Undo the most recently undone operation in the given context

Parameters:

context - - the context to be undone, or null if the context does not matter

monitor - - the progress monitor to be used for the undo, or null if no progress monitor is provided.

Returns:

the IStatus indicating whether the undo succeeded. The severity code in the returned status describes whether the operation succeeded and whether it remains in the history. OK severity indicates that the undo operation was successful and that the operation has been placed on the redo history. Listeners will receive the undone notification. CANCEL severity indicates that the user cancelled the operation and that the operation remains in the undo history. ERROR severity indicates that the operation could not successfully be undone and that it has been removed from the history. Listeners will be notified that the operation was removed. Any other severity code is not interpreted by the history, and is simply passed back to the caller. For all severities other than OK, listeners will receive the operationNotOK notification instead of the undone notification.

public org.eclipse.core.runtime.IStatus undoOperation(IOperation operation,
                                                      org.eclipse.core.runtime.IProgressMonitor monitor)

Undo the specified operation

Parameters:

operation - - the operation to be undone

monitor - - the progress monitor to be used for the undo, or null if no progress monitor is provided

Returns:

the IStatus indicating whether the undo succeeded. The severity code in the returned status describes whether the operation succeeded and whether it remains in the history. OK severity indicates that the undo operation was successful and that the operation has been placed on the redo history. Listeners will receive the undone notification. CANCEL severity indicates that the user cancelled the operation and that the operation remains in the undo history. ERROR severity indicates that the operation could not successfully be undone. The operation will remain at its current location in the history, and callers must explicitly remove it if desired. Any other severity code is not interpreted by the history, and is simply passed back to the caller. For all severities other than OK, listeners will receive the operationNotOK notification instead of the undone notification.

public boolean canRedo(IOperationContext  context)

Return whether there is a redoable operation available in the given context.

Parameters:

context - - the context to be checked, or null for any context

Returns:

true if there is a redoable operation, false otherwise.

public boolean canUndo(IOperationContext  context)

Return whether there is an undoable operation available in the given context

Parameters:

context - - the context to be checked, or null to represent any context

Returns:

true if there is an undoable operation, false otherwise.

public void addOperationApprover(IOperationApprover approver)

Add the specified approver to the operation history.

Parameters:

approver - - the IOperationApprover that will be consulted before any operation in the history is undone or redone

public void addOperationHistoryListener(IOperationHistoryListener listener)

Add the specified listener to the operation history.

Parameters:

listener - - the IOperationHistoryListener to receive notifications about changes in the history or operations that are executed, undone, or redone

public void removeOperationApprover(IOperationApprover  approver)

Remove the specified operation approver from the operation history.

Parameters:

approver - - the IOperationApprover to be removed

public void removeOperationHistoryListener(IOperationHistoryListener listener)

Remove the specified listener from the operation history.

Parameters:

listener - - The IOperationHistoryListener to be removed

public IOperation getRedoOperation(IOperationContext  context)

Get the operation that will next be redone in the given context. This method is used to retrieve the label or description as needed for the "Redo" menu.

Parameters:

context - - the context for the redo, or null if the context does not matter

Returns:

the operation to be redone or null if there is no valid operation available.

public IOperation [] getRedoHistory(IOperationContext  context)

Get the array of operations in the redo history for a given context. The operations are in the order that they would be redone if successive "Redo" commands were invoked.

Parameters:

context - - the context for the redo, or null if the entire history is requested

Returns:

the array of operations in the history

public IOperation getUndoOperation(IOperationContext  context)

Get the operation that will next be undone in the given context. This method is used to retrieve the label or description as needed for the "Undo" menu.

Parameters:

context - - the context for the undo, or null if the context does not matter

Returns:

the operation to be undone or null if there is no operation available.

public IOperation [] getUndoHistory(IOperationContext  context)

Get the array of operations that can be undone in the specified context. The operations are in the order that they would be undone if successive "Undo" commands were invoked.

Parameters:

context - - the context for the undo, or null if the entire history is requested

Returns:

the array of operations in the history

public void dispose(IOperationContext context,
                    boolean flushUndo,
                    boolean flushRedo)

Dispose of the specified context in the history. All operations that have only the given context will be disposed. References to the context in operations that have more than one context will also be removed.

Parameters:

context - - the context to be disposed, or null if all contexts are to be disposed

flushUndo - - true if the context should be flushed from the undo history, false if it should not

flushRedo - - true if the context should be flushed from the redo history, false if it should not.

public int getLimit()

Return the specified limit on the undo and redo history.

Returns:

limit

public void setLimit(int limit)

Set the limit on the undo and redo history.

Parameters:

limit - - the maximum number of operations that should be kept in the history

IOperationContext

public java.lang.String getLabel()

Get the label that should be used to describe the context in any views. Contexts may be shown when filtered operation histories are shown to the user.

Returns:

the label for the context.

public IContextOperationApprover getOperationApprover()

Get the operation approver that is used to approve undo or redo operations involving this context. A null context signifies that no special approval is necessary.

Returns:

the operation approver for the context.

public boolean acceptOperation(IOperation operation)

Return a boolean that indicates whether this context should be assigned to the specified operation. This method should be overridden by contexts that have complex rules for whether they should be assigned to an operation. Simple contexts generally answer true.

Returns:

a boolean indicating whether this context should be assigned to the operation.

public boolean equals(IOperationContext context)

Return whether this context is equal to the specified context.

Parameters:

context -

Returns:

true if the contexts are equal, false otherwise.

 

IOperationApprover

IOperationApprover defines an interface for approving the undo or redo of a particular operation within an operation history. Operations that are candidates for undo or redo have already been validated against their current state and according to the rules of the history.

 

By the time an IOperationApprover is consulted, the undo has already been requested. Approvers should true if the operation should proceed, and false if it should not. When an operation is rejected, it is expected that the object rejecting the operation has already consulted the user if necessary or otherwise provided any necessary information to the user about the rejection.

public org.eclipse.core.runtime.IStatus proceedUndoing(IOperation operation, IOperationHistory history)

Return a status indicating whether the specified operation should be undone. Any status that does not have severity IStatus.OK will not be approved. Implementers should not assume that the undo will be performed when the status is OK, since other operation approvers can veto the undo.

Parameters:

operation - - the operation to be undone

history - - the history undoing the operation

Returns:

the IStatus describing whether the operation is approved. The undo will not proceed if the status severity is not OK, and the caller requesting the undo will be returned the status that caused the rejection. Any other status severities will not be interpreted by the history.

public org.eclipse.core.runtime.IStatus proceedRedoing(IOperation operation, IOperationHistory history)

Return a status indicating whether the specified operation should be redone. Any status that does not have severity IStatus.OK will not be approved. Implementers should not assume that the redo will be performed when the status is OK, since other operation approvers may veto the redo.

Parameters:

operation - - the operation to be redone

history - - the history redoing the operation

Returns:

the IStatus describing whether the operation is approved. The redo will not proceed if the status severity is no OK, and the caller requesting the redo will be returned the status that caused the rejection. Any other status severities will not be interpreted by the history.

IOperationHistoryListener

public void historyNotification(OperationHistoryEvent event)

Something of note has happened in the IOperationHistory. Listeners should check the supplied event for details.

Parameters:

event - - the OperationHistoryEvent that describes the particular notification.

OperationHistoryEvent

public IOperationHistory history
 
public IOperation operation
 
public boolean operationAdded()

Returns whether or not an operation was added to the history. Listeners typically use this to add their context to a new operation as appropriate or otherwise record the operation.

Returns:

true if an operation has been added, false if not.

public boolean operationRemoved()

Returns whether or not an operation was removed from the history. Listeners typically remove any record of the operation that they may have kept in their own state.

Returns:

true if an operation has been removed, false if not.

public boolean aboutToExecute()

Returns whether or not an operation is about to execute. Listeners should prepare for the execution as appropriate. Listeners will receive a done notification if the operation is successful, or an operationNotCompleted notification if the execution is cancelled or otherwise fails. This notification is only received for those operations executed by the operation history. Operations that are added to the history after execution do not trigger these notifications. If the operation successfully executes, clients will also receive a notification that it has been added to the history.

Returns:

true if an operation is about to execute, false if not.

public boolean aboutToUndo()

Returns whether or not an operation is about to be undone. Listeners should prepare for the undo as appropriate. Listeners will receive an undone notification if the operation is successful, or an operationNotCompleted notification if the undo is cancelled or otherwise fails.

Returns:

true if an operation is about to undo, false if not.

public boolean aboutToRedo()

Returns whether or not an operation is about to be redone. Listeners should prepare for the redo as appropriate. Listeners will receive a redone notification if the operation is successful, or an operationNotCompleted notification if the redo is cancelled or otherwise fails.

Returns:

true if an operation is about to redo, false if not.

public boolean done()

Returns whether or not an operation was initially done. Listeners can take appropriate action, such as revealing any relevant state in the UI. This notification is only received for those operations executed by the operation history. Operations that are added to the history after execution do not trigger this notification. Clients will also receive a notification that the operation has been added to the history.

Returns:

true if an operation has been done, false if not.

public boolean undone()

Returns whether or not an operation was undone. Listeners can take appropriate action, such as revealing any relevant state in the UI.

Returns:

true if an operation has been undone, false if not.

public boolean redone()

Returns whether or not an operation was redone. Listeners can take appropriate action, such as revealing any relevant state in the UI.

Returns:

true if an operation has been redone, false if not.

public boolean operationNotOK()

Returns whether or not an operation was attempted and not successful. Listeners typically use this when they have prepared for an execute, undo, or redo, and need to know that the operation did not successfully complete. For example, listeners that turn redraw off before an operation is undone would turn redraw on when the operation completes, or when this notification is received, since there will be no notification of the completion.

Returns:

true if an operation has been redone, false if not.

 

UI Support Classes

Additional support classes will be provided in the package org.eclipse.ui.operations.  These classes handle policy decisions about the undo model to be used by the workbench.  Access to the undo and redo commands, as well as the operations history, will be provided through workbench.

 

Workbench Operation History

The workbench will manage a common operation history that can be used by any plug-in to integrate its undo and redo history with the workbench.  This operation history will be accessible through workbench API, to be determined by M5.

 

Contexts will be defined for text editors (with specialization for source editors) and for the workspace.  Throughout the development of R3.1, plug-ins will be encouraged to define operations that represent their existing actions or command handlers.  Additional contexts may be defined by plug-ins.

 

Existing workbench actions will be modified to support undo by mapping existing workbench actions and handlers to operations.  The operations will be added incrementally once the framework is in place. 

 

Operations that are assigned the workspace context will implement a subinterface, IWorkspaceOperation.  IWorkspaceOperation adds protocol to access the model elements manipulated by an operation (getElements()), so that listeners can decide if the workspace operation affects their model.  This protocol is not generalized to IOperation.  Prior experience with model-based operation frameworks has shown that there needs to be a very specific contract regarding how and when validation is performed, what underlying model listeners are used to validate the operation, and whether operations history notifications or internal model notifications are used to maintain the validity of the operation and the undo stack.  These details for IWorkspaceOperation will evolve as more workbench operations are created.

UndoHandler and RedoHandler

The Edit>Undo and Edit>Redo commands should be handled by any parts that wish to support undo.  Common handlers for the undo and redo commands will be provided.  These handlers can be assigned a context that should be used to filter the undo and redo history.  The handlers are responsible for the following:

Undo and Redo Toolbar Items

Time permitting, classes that support toolbar dropdown items for undo and redo will be developed.  These will also be assigned a context.  By clicking on the drop-down arrow, the user will see the history for that particular context.  The user will only be able to select a range, starting from the top item, to be undone or redone.  It is expected that the ability to view the history is more valuable to the user than the ability to multi-select operations for undo.

Operation Approvers

The workbench will provide and install an IOperationApprover that consults the contexts of an operation to determine whether undo or redo should proceed.  The policy is described as follows:

 

The workbench will allow undo or redo of any valid operation in the history, as long as there are no more recent operations in the history that share a context with the operation to be executed.   If the operation to be undone or redone has contexts that are also present in operations appearing later in the history, then the undo or redo of the operation will not be permitted.

 

A concrete example will help explain this. 

  1. The user makes local edits in editor A.
  2. The user initiates a refactoring operation whose context is “A” and “workspace.”
  3. The user makes additional local edits to editor A.
  4. The user goes to the navigator and selects Undo.

 

In the proposed implementation, the navigator requests an undo for the workspace context.  The refactoring operation triggered in the editor is the most recent operation that has the workspace context, but it also contains context “A.”  Since subsequent operations in the history also have context “A,”, the operation will not be allowed.  An explanation will be provided to the user after the fact:  “Cannot undo the refactoring operation because there have been subsequent changes to A.” 

 

Future releases may warn the user or provide a preference to determine whether the undo should proceed anyway, and whether the subsequent changes to “A” should also be undone, or be flushed from the history. Early prototypes of the framework showed that such warnings interrupt workflow and can be very difficult to understand, so the initial UI will be to prohibit the undo, explaining why.  

Migration Examples

The following examples explain how existing code can be migrated to use the operations framework.  An early prototype used these techniques to integrate the SDK text editor, refactoring framework, and sample applications into a common undo framework. 

Mapping existing actions or command handlers to operations

Converting an existing action to use operations is straightforward, apart from implementing the undo and redo behavior for the action.  The run() or runWithEvent method inside the action should create an operation, execute it, and add it to the operations history, rather than run the code inside the method.  The following code shows the existing run() method in the EditorAction of the readme tool example (org.eclipse.ui.examples.readmetool):

 

public void run() {
            String editorName = MessageUtil.getString("Empty_Editor_Name"); 
            if (activeEditor != null)
                editorName = activeEditor.getTitle();
            MessageDialog
                    .openInformation(
                            shell,
                            MessageUtil.getString("Readme_Editor"), 
                            MessageUtil.format("ReadmeEditorActionExecuted", 
                               new Object[] { getText(), editorName })); 
        }
Using operations, the run method simplifies:
 
        public void run() {
            String editorName = MessageUtil.getString("Empty_Editor_Name"); 
            if (activeEditor != null)
                editorName = activeEditor.getTitle();
            // create the operation
            IOperation operation = new EditorOperation(
               getText(),shell, editorName);
            // execute (and add to the history)
            history.execute(operation, null);
        }
The operation encapsulates the old run behavior, as well as the undo and redo for the operation:
 
class EditorOperation extends AbstractOperation {
        Shell fShell;
        String fEditorName;
        public EditorOperation(String label, Shell shell, String editorName) {
            super(label);
            fShell = shell;
            fEditorName = editorName;
            
        }
        public IStatus execute(IProgressMonitor monitor) {
            MessageDialog.openInformation(
                    fShell,
                    MessageUtil.getString("Readme_Editor"), 
                    MessageUtil.format("ReadmeEditorActionExecuted", 
                       new Object[] { getLabel(), fEditorName }));  
            return Status.OK_STATUS;
        }
        public IStatus undo(IProgressMonitor monitor) {
            // implement the undo here
            return Status.OK_STATUS;
        }
        public IStatus redo(IProgressMonitor monitor) {
            // implement the redo here
            return Status.OK_STATUS;
        }
    }
 
If an IHandler is provided for a command instead of using actions, the execute method of the handler is mapped similarly to the run method in an action:
 
        public Object execute(Map params) throws ExecutionException {
        try {
            IEditorPart activeEditor = params.get("ACTIVE_EDITOR");
            Shell shell = params.get("SHELL");
            String label = params.get("NAME");
            String editorName = MessageUtil.getString("Empty_Editor_Name"); 
            if (activeEditor != null)
                editorName = activeEditor.getTitle();
            // create the operation
            IOperation operation =new EditorOperation(label, shell, editorName);
 
            // execute (and add to the history)
            history.execute(operation, null);
        } catch (Exception e) {
            throw new ExecutionException(
                    "While executing the operation, an exception occurred", e); 
        }
        return null;
    }
 

When an action launches a wizard. then the operation is typically created as part of processing the “Finish” button in the wizard.  Some restructuring may be required.  For example, wizards are often implemented in hierarchies and make use of convenience methods in the wizard hierarchy.  Some of these methods may have to move to a corresponding hierarchy of operations.

Refactoring example:  mapping existing protocol to IOperation

In the current SDK implementation, org.eclipse.ltk.core.refactoring provides an undo framework for undoing refactoring operations.  This framework is based on the notion of “Change” objects.  Change objects that can be undone are responsible for returning the undo version of a change when they are executed.  An undo stack is maintained by an internal undo manager.  This undo manager invalidates the history whenever an unknown workspace change occurs.  Undo-aware objects send signals to the undo manager as they perform operations, so that the undo manager will not invalidate the history.

 

A prototype integrated the refactoring change framework with the operations framework as follows:

1.       The change objects were wrappered with a class that implements IOperation (and IWorkspaceOperation) and maps the operation protocol to the Change protocol.

2.       The refactoring undo manager was replaced with an alternate implementation that uses the operations history to maintain the undo and redo history.

3.       The workspace listener and validation strategy used in the Change framework was maintained since the timing of the notifications was critical.  Additional integration work could be done to use the operations history listeners for the same purpose, or to change the validation strategy as more workspace operations are supported outside of refactoring.

4.       Unrelated listeners (such as text editors) listened for new operations being added.  These listeners could check the elements affected by the workspace operation and determine whether another context should be assigned to the operation.

5.       Refactoring operations could be undone and redone from any view or editor that installed the undo and redo handlers on the workspace context.

Text editor example:  implementing IOperation in preexisting commands

The JFace text framework supports undo and redo of text editing operations.  This implementation relies on private undo stacks that are maintained by each editor.  The JFace IUndoManager listens to text changes coming from the underlying widget, and builds a TextCommand for each undoable edit. 

 

A prototype integrated the text undo in JFace with the operations framework as follows:

  1. The existing TextCommand was altered to implement the IOperation interface.
  2. Specialized contexts (one instance for each text editor) are assigned to the operations.
  3. The existing UndoManager was replaced with an alternate implementation that added text commands to the operations history instead of a local stack, and used the operation history protocol when handling undo and redo commands.
  4. The undo manager installs a listener on the history to monitor new operations that are added to the history.
  5. When an “outside” operation is added to the history, the undo manager consults the editor’s operation context to see if the outside operation is relevant.  The editor context can check the elements affected by workspace operations and add itself to the operation’s context if appropriate.