Last
updated:
Status: Under investigation (see bug 37716)
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 many general problems as independently developed plug-ins implement
their own undo strategies:
-
Undo
and redo actions appear in different menus.
-
Undo
and redo implementations can have different semantics. Different approaches to undo by different
plug-ins may confuse the user if they choose different semantics. For example:
o
Is
undo strictly linear?
o
If
it is not linear, what techniques are used to undo an operation? Implementation models for non-linear undo (rollback
and perform the history again, dynamic reversal) can affect the user outcome.
o
What
is the scope of an undo? Is it global to
the system? Is it local to the
window? Is it model-dependent?
-
Plug-ins
that want to add undo support may have to choose between different undo
strategies and implementations provided by their required plug-ins, or worse
yet, implement yet another strategy.
While
implementation of a centralized undo mechanism should be straightforward, it
has not been provided in previous releases due to problems in defining the
proper undo semantics across different plug-ins. The challenge in defining consistent, predictable
undo semantics can best be understood by looking at the two different undo
models provided in the R3.0.1 release of the base Eclipse SDK.
The SDK
currently contains two undo implementations and user commands:
-
Text
undo (Edit>Undo) provides a linear undo model for text-level
operations. That is, textual insert/replace/delete
operations can be undone in the reverse order in which they are applied.
-
Refactoring undo (Refactor>Undo) allows an
undo of the last refactoring operation, but only when
the workspace has not been otherwise modified since the last refactoring.
In
discussing the appropriate semantics for workbench undo, it is useful to
introduce terminology that is independent of any particular implementation for
undo support.
Undo
implementations must provide a history of operations performed by the user and
some knowledge of the “context” for these operations. For example, the text editor undo operates
within the context of a single document, while refactoring
undo operates within the context of some workspace resources.
The context
for an operation helps determine where the undo for that operation is
available. When the contexts for different
operations are completely separate, the semantics for undoing an operation from
either context are easily understood.
Undoing an operation from one context does not affect the operation
history from the other context.
Even when
completely separate undo contexts exist, the scope of the operation history and
resulting undo operation must be defined.
There are a few different alternatives:
-
The
UI that manipulates each separate undo context provides its own undo command. This is done in R3.0.1 with Edit>Undo and Refactoring>Undo.
This does not scale well, but makes it clear which context is to be
undone by forcing the user to explicitly choose the context for the undo. We call this an explicit local undo scope.
-
A
unified undo command exists and its meaning is interpreted according to some
current context (such as the active part or the currently edited model). The unified command only ever operates on the
current context. This is analogous to
retargeted global actions in the Eclipse workbench. We call this implicit local undo scope.
-
A
unified undo command exists and its meaning is global for all operations
performed in the system. The history
list and default operation to be undone have no relationship to the user’s
current context. We call this global undo scope.
The
appropriate scope to be used depends on what the user is doing. When the operations performed by a user are sequential
steps in a larger task, a global scope may be appropriate. When a user is performing separate tasks
while working in separate views, a more localized scope is appropriate. The decision is largely subjective and often
cannot be predicted by the system. [1]
Unfortunately,
the SDK model for undo is more complicated.
The contexts for edit and refactoring undo are
not completely separate. The user can
trigger refactoring changes from the editor
itself. These changes can manipulate
both the document being edited and other workspace resources. Further, these changes occur alongside
localized text editing changes, creating an operation history with multiple
contexts that are initiated from a single part.
Even when a
local undo scope is employed, there are different possibilities for
interpreting “undo” when a refactoring operation is
triggered from an editor:
Examining
scenarios and the desired behavior shows that any one (or more) of the three
alternatives are desirable at different times, depending on what the user is
trying to achieve. Appendix A collects
various refactoring and edit scenarios to help
demonstrate the challenge.
The
appearance of a simple Edit>Undo command in the UI typically implies a
linear undo. That is, the most recent operation
executed is undone. Descriptive undo
labels (such as “Undo Typing” or “Undo Rename”) help to remind the user of the
last “undoable” operation.
As we’ve
shown in discussing scopes and contexts, a strictly linear global undo is not
the ideal behavior when working across contexts. Once an attempt is made to choose the
“appropriate” operation to be undone in the user’s current context, it’s
possible that the chosen operation is not the most recently executed operation. Decisions must be made about how to handle
more recently executed operations. A
strictly linear model would force more recent operations to be undone before
allowing the chosen operation to be undone.
In a
selective undo approach, users are given an opportunity to view the history
list and select the operation to be undone.
Operations that can be undone in the current state of the application
are available for undo at any time, even if there are more recent operations in
the history.
The
workbench support should minimally be a hybrid of these approaches. When the operation appropriate for one
context is not the most recently executed one, then the user (by prompt or by
preference) should have input into the decision to roll back any more recent operations
or perform the undo selectively and retain the rest of the history list.
While the
discussion and scenarios surrounding scopes and contexts have focused on the
“hard cases,” it should be realized that most users will seek out “undo” in the
simple cases. Single context, strictly
linear undo, particularly in text editing, is provided in most popular
applications. The availability of a more
selective undo for the “hard cases” should not hinder the expected behavior of
the simple cases.
Support for
undo should be “pay as you go,” in terms of efficiency, burden on the user, and
burden on the implementer. Validation of
operations in the history list against the current state of the application
should be on demand, rather than forcing operation objects to watch the current
state of the application and react to changes (even when the user might never
choose “undo.”)
Undo
support should not assume the presence of a user interface. The operations history and associated
interfaces can be defined independent of the UI.
With these
goals in mind, the following implementation is proposed:
A framework
for defining executable, undoable operations (IOperation)
will be provided in a core package, org.eclipse.core.operations. As actions are run, an object describing the
operation to be performed is 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 as a unit, and can never be partially undone.
Operations
can be assigned one or more contexts (IOperationContext)
to which they apply. 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 (an editor’s
undo context is the editor part itself).
In other cases, a model object may serve as the context (the navigator’s
context is the workspace). The interface leaves this to the discretion of the
implementer and requires only that equality of contexts can be
established. User-appropriate labels and
descriptions for operation contexts are required so that any filtered operation
history views can appropriately label the contexts that are filtering the view.
Contexts
can be assigned in multiple ways:
1. When the triggering object knows the
context of the operation, the context can be assigned up front. 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. Depending on the structure of the
code, the object that creates the operation object may not know what context is
appropriate, or if other contexts should be included. In these cases, a listener can be added to
the operations history. When an operation
is added to the history, listeners can decide if their context should be added
to the operation.
Representing
the contexts for an operation as a collection (empty graph) assumes that there
is no inherent relationship between operation contexts. This allows the most flexibility in defining
contexts. For example, an editor may perform
an operation that affects both the editor’s document and the workspace at
large. These will be considered two
independent contexts and assigned to the operation. One could argue that the context for the
document is simply a refinement of the workspace context, and that a
hierarchical relationship between those contexts should exist. Without more use cases, we avoid establishing
any relationship between contexts at this time
Operation contexts
that are used when manipulating well-known models should be accessible by
API. This can be done in multiple ways:
The
following interfaces represent the operation, the operation history, and the operation
context.
public interface IOperation {
void addContext(IOperationContext); //
add the context to the operation’s contexts
boolean hasContext(IOperationContext); //answer
whether the receiver’s contexts contain the context
List getContexts(); // answer
the list of contexts assigned to the receiver.
IStatus canExecute(); // answer whether
the receiver can be executed
IStatus canRedo(); // answer
whether the receiver can be redone
IStatus canUndo(); // answer
whether the receiver can be undone
void execute(IProgressMonitor); // execute the operation
void redo(IProgressMonitor); // redo the operation
void undo(IProgressMonitor); // undo the operation
void dispose(); //
the operation is no longer needed
String getLabel(); // provide
a label to name this operation for the user
String getDescription(); // provide a
description of this operation for the user
}
public interface IOperationHistory
{
void add(IOperation); // add
the specified operation to the history
void redo(IOperationContext,
IProgressMonitor);
//
perform the redo operation for the given context
void undo(IOperationContext,
IProgressMonitor);
//
perform the undo operation appropriate for the given context.
IStatus canRedoIn(IOperationContext); //
answer whether redo is available in the given context
IStatus canUndoIn(IOperationContext); //
answer whether undo is available in the given context
void addOperationHistoryListener(IOperationHistoryListener);
//
add a listener for changes to the operation history
void removeOperationHistoryListener(IOperationHistoryListener);
//
remove the specified history listener from the receiver.
IOperation getRedoFor(IOperationContext); //
get the redo operation for the given context
IOperation [] getRedoHistoryFor(IOperationContext); //
answer the redo history list for the given context
IOperation getUndoFor(IOperationContext); //
answer the undo operation for the given context
IOperation [] getUndoHistoryFor(IOperationContext); //
answer the undo history list for the given context
void disposeAll(IOperationContext);
//
Remove all operations that have only the specified context. Implementers must
//
determine how to handle operations that have additional contexts.
void setLimit(int);
//
set a limit on the operation history.
When the limit is reached, the oldest operation
//
will be disposed to make room for new operations.
}
public interface IOperationHistoryListener
{
void operationAdded(IOperation); //
an operation has been added to the history
void aboutToUndo(IOperation); //
the operation is about to be undone
void undone(IOperation); // the
operation was undone
void aboutToRedo(IOperation); //
the operation is about to be redone
void redone(IOperation); // the
operation was redone
}
public
interface IOperationContext {
IOperationApproval getOperationApproval();
String getDescription();
String getLabel();
boolean
equals(IOperationContext);
}
public
interface IOperationApproval {
boolean proceedUndoing(IOperation);
boolean proceedRedoing(IOperation);
}
An
operation is responsible for validating its ability to execute and undo against
the current state of the application.
Validation may be requested even when an operation is not the most
recently executed operation (or most recently undone operation). This is important since operations in
unrelated contexts may coexist in the same history unaware of one another’s
existence.
As
operations are executed, they are added to the operation history. Note that the operation history does not necessarily
perform the initial execution, but rather assumes the operation is already
executed before it is added to the history.
This is necessary for text editing operations, where the operations are
happening as the user types and the recording/batching of operations for the
history occur afterwards.
The operation
history is responsible for implementing the application’s undo model, and the
decisions about handling multiple contexts.
If an application chooses to implement a strictly linear, global undo,
then its implementation of IOperationHistory can
ensure that requests to perform undo or redo in a particular context will fail
if the most recent operation does not have that context.
More
flexible undo models may be implemented with the assistance of the IOperationApproval that is assigned to an operation context. This interface allows other contexts to be
consulted when an operation is to be executed or undone. The undo model defined by the operation
history determines when to consult the IOperationApproval. Possible implementations will be discussed in
more detail in the workbench scenarios.
The
workbench will adopt a hybrid undo approach that allows selective undo across
multiple contexts. The workbench will
allow undo/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/redone has contexts that are also present in operations appearing
later in the history, then the IOperationApproval for
the contexts that have the conflicts will be consulted.
A concrete
example will help explain this.
-
The
user makes local edits in editor A.
-
The
user initiates a refactoring operation whose context
is “A” and “workspace.”
-
The
user makes additional local edits to editor A.
-
The
user goes to the navigator and selects Undo.
In the
proposed implementation, the navigator would request an undo for the context
“workspace.” 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 IOperationApproval for
context “A” is consulted as to whether the undo should proceed. The IOperationApproval
supplied by context “A” could do one of
the following:
-
allow
the undo to proceed, leaving the local changes to “A” in the undo history.
-
quietly
undo the subsequent local changes to “A” and then allow the original undo to
proceed.
-
switch
to the editor and non-quietly undo the local changes to “A” before allowing the
original undo to proceed.
-
prompt
the user for the preferred action.
-
provide
a user preference that drives the behavior of the scenario.
To support
the existing workbench undo scenarios, there will be two kinds of contexts
defined in the workbench IDE.
Further
definition of contexts and the supported operations are required:
-
The
complete set of workspace operations (besides refactoring)
that should be undoable must be defined.
-
Any
additional contexts required by the SDK (Team?
Debug?) must be defined.
-
The
operation contexts for the major views in the SDK must be defined.
-
The
semantics for assigning multiple contexts must be defined. For example, do refactoring
operations that affect an open editor (but were triggered elsewhere) carry the
context of that editor? Should the
editor decide this by listening for new operations that are added to the
history?
The
following are working assumptions about the undo operations:
-
There
is no relationship between a perspective and an operation context, since
switching perspectives will change the active part, and thus may or may not
affect the current context.
-
Actions
that only change the presentation in the workbench (open a view, switch
perspectives, change the sort order) are not considered to be operations since
they are easily undone and redone
through the same window mechanics.
The Edit>Undo
and Edit>Redo commands should be retargeted by most of the major views (list
of views TBD). If a view retargets
Undo/Redo, then it knows what operation contexts it supports, and it uses these
contexts to retrieve the available undo operation.
Edit>Undo
is enabled based on the active part.
IStatus
status = operationHistory.canUndoIn(myContext);
// up to the active part to define
behavior for anything between OK and FATAL.
The label
for the undo action should be appended with a description of the operation.
operationToUndo
= operationHistory.getUndoFor(myContext);
label = “Undo “+ operationToUndo.label();
Undo and
Redo on the toolbar include a drop down that shows the operation history. This history depends on the current part’s
context.
operationHistory.getUndoHistoryFor(myContext);
The drop
down permits range selection of operations from the top down (see MS Word and
other applications). This UI implies a
linear undo within any particular set of contexts.
The UI for
handling special cases should be determined.
Consider the following scenarios:
Scenario #1:
Mixed contexts in an editor, undo from another view
-
The
user makes local edits in editor A.
-
The
user initiates a refactoring operation whose context
is “A” and “workspace.”
-
The
user makes additional local edits to editor A.
-
The
user goes to the navigator and selects Undo.
The IOperationApproval for the editor’s context can implement
UI for handling this case if desired.
Possible options include:
-
warn
the user about the local changes in A and ask whether to undo them or leave
them
-
provide
a user preference that drives the behavior of the scenario.
The exact
solution will be prototyped and may involve both a first-time prompt and
subsequent preference (“Don’t ask me again”).
Scenario #2:
Editor undo triggers a non-local operation
-
The
user makes local edits in editor A.
-
The
user initiates a refactoring operation whose context
is “A” and “workspace.”
-
The
user makes additional local edits to editor A.
-
The
user selects undo repeatedly from the editor
Most operations
in the editor’s context affect only the editor.
The refactoring operation affects not only the
editor, but other objects in the workspace.
It might surprise the user for the refactoring
to be undone. The editor could implement
a warning dialog when it detects that the proposed undo operation has
additional contexts.
If the refactoring had been triggered elsewhere (say, editor “B”
or the navigator), but resulted in changes to editor “A,” it is even more
important that the user be warned about the operation invoked by the undo
command.
The solution for this scenario will be prototyped
and may involve both a warning and subsequent preference. For example, a prompt could provide the label
and description of the operation and warn that it affects other views. The user could choose whether to proceed, and
the choice could be remembered in a preference.
It appears
that the proposed framework allows detection of these cases and hooks for
supplying the necessary UI.
More
selective undo approaches can be supported by the framework, but the ability to
support them in the workbench depends upon the implementation of the individual
operations and their ability to be undone independently. The priority is TBD. Ideas include:
Additional menu
commands show the operation history.
Edit>Undo… shows the undo operation history based on the
active part’s context.
Edit>Redo… shows the redo operation history based on the
active part’s context.
operationHistory.getUndoHistoryFor(myContext);
The operation
history can be shown in a view or dialog.
Operations that are valid can be selected (one at a time) and undone (or
redone) regardless of sequential order. Multiple
selections are not allowed since the validation state for an operation might
change as other operations are undone.
The operation
history view could allow changing of the filtering. While the default filtering could depend on
the active part’s context, the user could be shown the available contexts and
choose one or more contexts (or none at all) that affect the filtering of the
list.
Note that
the UI for the advanced cases can be implemented independently from the operations,
and may be useful for testing the framework.
JFace text
currently has API for retrieving an IUndoManager from
a text viewer. The undo manager is
connected to a text viewer and watches the changes that happen in the
viewer. As editing actions occurs,
commands (TextCommand) are created and then
“committed” to a command stack.
TextCommand
could be changed to implement IOperation. The mapping from IOperation
to TextCommand protocol is straightforward. Currently the TextCommand
directly pushes itself onto a local command stack of a viewer when a set of pending
changes are committed. Instead, it would
need to set its context to one appropriate for the text viewer and add itself
to the workbench operation history.
IUndoManager API could be mapped as follows:
connect(ITextViewer)
Creates a
context (myContext) appropriate for representing this
document’s operations
disconnect(ITextViewer)
Performs
necessary disconnect behavior and clears the operation history of operations involving
the viewer.
operationHistory.disposeAll(myContext);
beginCompoundChange()
Creates a CompoundOperation and stores subsequent TextCommands
in this operation.
endCompoundChange
Adds the current
compound operation to the operation history.
reset()
Clears the operation
history of operations involving the viewer.
operationHistory.disposeAll(myContext);
setMaximumUndoLevel(int) – TBD
undoable()
Maps as
follows:
status = operationHistory.canUndoIn(myContext);
return status.isOK(); // may require further checking of other
cases
redoable()
Maps as
follows:
status = operationHistory.canRedoIn(myContext);
return status.isOK(); // may require further checking of other
cases
undo()
Use operationHistory.performUndoFor(myContext)
instead of managing its own stack.
redo()
Use operationHistory.performRedoFor(myContext)
instead of managing its own stack.
The TextCommand class is private, so no API mapping is
discussed here. However, its protocol is
very similar to IOperation and the
reimplementation/mapping is straightforward.
Issues:
-
As
discussed throughout this document, additional operations (such as compound operations
or refactoring operations) might appear in the text
viewer’s context, and therefore in its IUndoManager. The implementation should be checked for any
assumptions that the current operation is a TextCommand.
-
IUndoManager currently keeps a local history limit. How do we handle this?
-
allow
local history limits per context (seems complicated)
-
ignore
this part of the API in the new world
-
decide
how to affect the global history limit based on the local limit
Refactoring
undo is currently implemented with change objects, Change objects record workspace changes
initiated by refactoring operations. When a refactoring
change is performed, it can optionally return another change that could be used
to undo the change just executed. The
undo change is placed in refactoring’s IUndoManager.
Changes can be performed, but any undo or redo causes a new change to be
created vs. having each change know how to undo and redo itself.
While this
approach simplifies the protocol for an operation, it has not been used in the
proposed framework for several reasons:
Additional
protocol is provided for initializing data needed to do a live validation for
execution. Validation can be done with
this cached information or on the fly.
Protocol is
also provided to return the object modified by a change (getModifiedElement()).
The Change
API could be mapped as follows. Note
that much of the protocol could be provided in an abstract “RefactoringOperation”
or perhaps in a more generic “WorkspaceOperation.” However we do not promote these concepts to
the framework since different kinds of operations have different needs for
caching model objects, validation state, etc.
getModifiedElement()
getAdapter(Class)
Refactoring
changes need to know which model element is modified by the change. They must also implement IAdaptable. This protocol can be provided in an abstract RefactoringOperation and interpreted by individual
subclasses as done today. This protocol
might in fact be suitable for all workspace-affecting operations.
getParent()
setParent(Change)
isEnabled()
setEnabled(boolean)
The
associated fields and protocol can be defined in an abstract class.
initializeValidationData(IProgressMonitor)
This hook
allows changes to cache local validation data, or to hook up listeners to the
model and listen for changes that may invalidate the change. This protocol could be provided in the
abstract refactoring class.
dispose()
Maps
directly.
getName()
Maps to operation.getLabel
isValid(IProgressMonitor)
Maps to canUndo(), canRedo(), and canExecute(). See
discussion below.
perform(IProgressMonitor)
Maps to undo(IProgressMonitor), redo(IProgressMonitor)
and canExecute(IProgressMonitor). See discussion below.
Issues
-
Refactoring change objects do not support direct undo() or redo(). Rather, a change that supports undo() returns
a new change that “undoes” it whenever it is executed. The code for refactoring
changes would have to be refactored in one of these
ways:
o
Eliminate
the creation of the undo change and move this work into an undo() method.
o
Continue
to create the undo change when performing a change, but instead of returning
it, cache it in the change itself. Undo
protocol checks for the presence of this change and uses it for any undo
validation or execution requests.
-
Validation
of refactoring changes is assumed to be expensive, so
a progress monitor is provided in validation protocol. This concept is not promoted to the framework
since validity is checked when building the undo menu command. Is the progress monitor absolutely necessary
for validation vs. execution?
It is
likely that many views and editors in the workbench IDE will be aware of this special
kind of IOperation.
An additional interface, IWorkspaceOperation,
may evolve as refactoring changes are adapted to the
new operations framework. Patterns
surrounding the workspace operation will develop, particularly involving
operation contexts. For example, views
and editors (such as text editors) that are workspace-aware could listen to the
operations history. As new workspace
operations are added to the history, the modified element can be queried by the
listener. The listener can add its
operation context to the operation if desired.
GEF
supplies a Command framework and a CommandStack which
manages an internal undo and redo stack.
Commands are pushed onto the stack when they are executed by the stack. Most GEF command protocol can be mapped to
the proposed operation protocol. The GEF
commands are very similar to IOperation without the
notion of contexts.
Issues for
GEF arise in the differences between GEF’s command
stack and the proposed operation history.
We assume that GEF intends for GEF commands to coexist with other
workbench operations in a shared undo history.
If so, then the command stack must be replaced by the operation
history. Issues include:
isDirty()
markSaveLocation()
GEF
supports a marker (saveLocation) that becomes dirty
if any commands are added after the mark is made. If this concept is still needed in light of
the new framework, then GEF might have to mark the location inside its own
commands or using a dummy marker command.
execute(Command)
The GEF
command stack executes commands on behalf of the client and adds them to the
stack, while the operation history does not do the execution on behalf of the
client. GEF clients that create GEF
commands would alter the pattern “commandStack.execute(someCommand)” to instead use “someCommand.execute(null);
operationHistory.add(someCommand)”
addCommandStackListener(CommandStackListener)
GEF is
evolving its CommandListener interface in R3.1 to
include additional notification. This
must be reconciled with the proposed support in IOperationHistoryListener
and the ability to further validate commands using IOperationApproval.
The EMF
command framework is very similar to the GEF support and has similar issues to
those described above. Additional issues
arise for:
getResult();
This method
answers the result of command execution when the command is in certain
states. Since clients can execute an
operation explicitly in the framework, EMF code could query the result after
execution and store it in an EMF abstract operation. This protocol could then be provided in the
EMF operations.
getAffectedObjects();
This method
is used to highlight the appropriate objects in a view to show what the EMF command
did. It is not clear that this will be generally necessary. This protocol could be added in an EMF
operation abstract class.
As
discussed with refactoring changes, an abstract class
providing the mapped protocol will likely evolve, along with usage patterns for
listening for new EMF operations and assigning appropriate operation contexts.
The
following scenarios were used while discussing possible designs for a common
undo framework. Most of these scenarios involve conflicting
contexts or scopes. These scenarios,
involving mixed text edits and refactoring
operations, have complicated previous efforts to define a common undo
framework.
The
scenarios assume that an integrated undo in the workbench should present a
common “Undo” command rather than the separate commands employed today, and
that the “Undo” command is descriptive enough to imply the operation that will
take place.
For each
scenario described, we attempt to describe the desired outcome, the outcome in
R3.0.1 where applicable, and any interesting alternative outcomes.
Note that
some scenarios involve nearly the same user interactions, but are different in
desired outcome based on what the user was trying to accomplish while
performing the operations. Specific user
intent is described in order to clarify the scenario.
Scenario #1:
User is editing source file “A.”
While editing the document, some interrupt occurs causing the user to go
to the navigator and refactor some code unrelated to
A. When the refactoring
is complete, the user returns to the editor on “A” and notices a typo. The user selects Edit>Undo.
Desired
Outcome: The last text editing operation
made before the unrelated refactoring operation is
undone. The typo is corrected. The undo command was described as “undo
typing”
R3.0.1
Outcome: Desired Outcome.
Proposal
Outcome: Desired Outcome. (Unrelated refactoring
operation did not have the editor’s context).
Scenario #2:
User is editing source file “A.”
While editing the document, some interrupt occurs causing the user to go
to the navigator and refactor some code. The refactoring
operation updates references to the refactored code
in file “A.” When the refactoring is complete, the user returns to the editor on
“A” and notices a typo. The user selects
Edit>Undo.
Desired
Outcome: This scenario depends largely
on the user’s intent. If the typo was in
a location completely unrelated to the refactor, the
user expects the last text editing operation made before the refactoring operation to be undone, correcting the
typo.
R3.0.1
Outcome: The text edit caused by the refactoring operation is undone, but the refactoring itself is not undone. The typo remains until the user invokes
subsequent “Undo.”
Proposal
Outcome: This outcome can be implemented
in different ways. If the refactoring operation command is assigned the context for
“A,” since A was affected, then the refactoring
operation would be undone before the typo.
However, the editor could notice that the operation has multiple contexts
and prompt the user with an operation history.
Scenario #3:
User is editing source file “A.”
While editing the document, some interrupt occurs causing the user to go
to the navigator and refactor some code. The refactoring
operation updates references to the refactored code
in file “A.” When the refactoring is complete, the user recalls making a typo
just before the refactor. The user returns to the editor on “A” and
looks for the typo. It is not there, as
it has been replaced with new code due to the refactoring
operation. The user selects Edit>Undo.
Desired
Outcome: At this point, the user needs
information that helps explain the disappearance of the typo. In this case, there are two helpful options:
R3.0.1
Outcome: Desired Outcome #2.
Proposal
outcome: Desired Outcome #1. This scenario is the same as scenario #2
except for user intent, so the UI could present similar choices as described
above.
Scenario #4:
User is editing source file “A” and considers renaming a method in
“A.” While typing a new name for the
method, the user realizes that a refactoring
operation will simplify the editing process.
The user chooses “Refactor>Rename” to
rename the method. The refactor causes references to be updated in other
files. The user then decides that the
rename was not appropriate. The user selects
Edit>Undo.
Desired
Outcome: The refactoring
operation is undone, updating all of the references. The user is returned to
the typing state before the refactor. Selecting another Edit>Undo will undo the
typing of the new name and return to the old name.
R3.0.1
Outcome: The text edit implied by the refactoring operation is undone, and the user is returned
to the typing state before the refactor. However, the refactoring
itself is not undone. This is undoable
from a separate Refactor>Undo.
Proposal
outcome: Desired Outcome. A user prompt or verification may occur as
discussed in other scenarios.
Scenario #5:
User is editing source file “A” and considers renaming a method in
“A.” The user decides that a refactoring operation is in order. The user chooses “Refactor>Rename”
to rename the method. The refactor causes references to be updated in other
files. The user then makes additional
edits to the javadoc for the renamed method and
realizes that the rename was not appropriate.
The user selects Edit>Undo.
Desired
Outcome: The most recent editing changes
to the javadoc are undone. The user can continue selecting “Undo” until
the refactoring operation is undone, updating all of
the references.
R3.0.1
Outcome: The most recent editing changes
to the javadoc are undone. The user can continue selecting “Undo” until
the text edit implied by the refactoring operation is
undone. However, the refactoring
itself is not undone. This is undoable
from a separate Refactor>Undo.
Proposal
outcome: Desired Outcome.
Scenario #6:
User is editing source file “A” and considers renaming a method in
“A.” The user decides that a refactoring operation is in order. The user chooses “Refactor>Rename”
to rename the method. The refactor causes references to be updated in other
files. The user then proceeds to edit javadoc in another part of the file, unrelated to
change. The user realizes the refactor was not appropriate, and not wanting to undo the
edits to the javadoc, selects the navigator view, and
selects Edit>Undo, assuming that the navigator undo won’t affect local edit
changes.
Desired
Outcome: The refactoring
operation is undone, but the changes to the javadoc
made afterward are not undone. The local
changes to file “A” could only be undone while inside editor A.
R3.0.1
Outcome: The refactoring
operation is undone, and the most recent changes to the file are lost.
Proposal
outcome: This can be implemented in
different ways with IOperationApproval. The operation history would notice that the
chosen operation has context “A” and that later changes for “A” are in the
history. The IOperationApproval
assigned to context “A” could prompt the user as to whether to undo the more
recent changes in “A.”
[1] Literature addressing multi-user
undo [Abowd & Dix] addresses similar scoping
issues in a multi-user model.
Collaborating users may expect a global undo scope while users working
in separate contexts may not. Users may
be moving between collaboration and separation in a single work session, and the
system cannot detect which is the case.
Undo implementations in these systems typically keep track of the user
who initiated the operation within a global history and filter the history
appropriately. Potential conflicts are
brought to the attention of the user(s).