get rid of recursion on undo (fix EA-119527 - SOE: UndoManagerImpl.undo)

This commit is contained in:
Dmitry Batrak
2018-04-23 17:57:12 +03:00
parent 163884750f
commit 941e25e599
4 changed files with 28 additions and 32 deletions
@@ -210,6 +210,13 @@ public class CommandMerger {
boolean isInsideStartFinishGroup = false;
while ((undoRedo = createUndoOrRedo(editor, isUndo)) != null) {
if (editor != null && undoRedo.isBlockedByOtherChanges()) {
UndoRedo blockingChange = createUndoOrRedo(null, isUndo);
if (blockingChange != null && blockingChange.myUndoableGroup != undoRedo.myUndoableGroup) {
if (undoRedo.confirmSwitchTo(blockingChange)) blockingChange.execute(false, true);
break;
}
}
if (!undoRedo.execute(false, isInsideStartFinishGroup)) return;
isInsideStartFinishGroup = undoRedo.myUndoableGroup.isInsideStartFinishGroup(isUndo, isInsideStartFinishGroup);
if (isInsideStartFinishGroup) continue;
@@ -16,6 +16,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
@@ -79,7 +80,7 @@ abstract class UndoRedo {
protected abstract void setBeforeState(EditorAndState state);
public boolean execute(boolean drop, boolean isInsideStartFinishGroup) {
public boolean execute(boolean drop, boolean disableConfirmation) {
if (!myUndoableGroup.isUndoable()) {
reportCannotUndo(CommonBundle.message("cannot.undo.error.contains.nonundoable.changes.message"),
myUndoableGroup.getAffectedDocuments());
@@ -88,14 +89,12 @@ abstract class UndoRedo {
Set<DocumentReference> clashing = getStackHolder().collectClashingActions(myUndoableGroup);
if (!clashing.isEmpty()) {
if (!tryFallbackToGlobalUndo())
reportCannotUndo(CommonBundle.message("cannot.undo.error.other.affected.files.changed.message"), clashing);
reportCannotUndo(CommonBundle.message("cannot.undo.error.other.affected.files.changed.message"), clashing);
return false;
}
if (!isInsideStartFinishGroup && myUndoableGroup.shouldAskConfirmation(isRedo()) && !UndoManagerImpl.ourNeverAskUser) {
if (!disableConfirmation && myUndoableGroup.shouldAskConfirmation(isRedo()) && !UndoManagerImpl.ourNeverAskUser) {
if (!askUser()) return false;
}
else {
@@ -140,22 +139,6 @@ abstract class UndoRedo {
return true;
}
private boolean tryFallbackToGlobalUndo() {
UndoableGroup globalUndoableGroup = getStackHolder().findGlobalUndoableGroup(myUndoableGroup);
if (globalUndoableGroup != null) {
if (isRedo()) {
myManager.redo(null);
}
else {
myManager.undo(null);
}
return true;
}
return false;
}
protected abstract boolean isRedo();
private Collection<Document> collectReadOnlyDocuments() {
@@ -212,6 +195,17 @@ abstract class UndoRedo {
return isOk[0];
}
boolean confirmSwitchTo(@NotNull UndoRedo other) {
final boolean[] isOk = new boolean[1];
TransactionGuard.getInstance().submitTransactionAndWait(() -> {
String message = CommonBundle.message("undo.conflicting.change.confirmation.message") + "\n" +
getActionName(other.myUndoableGroup.getCommandName()) + "?";
isOk[0] = Messages.showOkCancelDialog(myManager.getProject(), message, getActionName(),
Messages.getQuestionIcon()) == Messages.OK;
});
return isOk[0];
}
private boolean restore(EditorAndState pair, boolean onlyIfDiffers) {
// editor can be invalid if underlying file is deleted during undo (e.g. after undoing scratch file creation)
if (pair == null || myEditor == null || !myEditor.isValid() || !pair.canBeAppliedTo(myEditor)) return false;
@@ -228,4 +222,9 @@ abstract class UndoRedo {
myEditor.setState(pair.getState());
return true;
}
public boolean isBlockedByOtherChanges() {
return myUndoableGroup.isGlobal() && myUndoableGroup.isUndoable() &&
!getStackHolder().collectClashingActions(myUndoableGroup).isEmpty();
}
}
@@ -12,7 +12,6 @@ import com.intellij.util.containers.WeakList;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
@@ -286,14 +285,4 @@ class UndoRedoStacksHolder {
}
}
}
@Nullable
public UndoableGroup findGlobalUndoableGroup(UndoableGroup undoableGroup) {
for (UndoableGroup group : myGlobalStack) {
if (group == undoableGroup) {
return group;
}
}
return null;
}
}
@@ -133,6 +133,7 @@ profiling.capture.snapshot.error=Failed to capture snapshot: {0}
cannot.undo.dialog.title=Cannot Undo
cannot.undo.error.other.affected.files.changed.message=Following files affected by this action have been already changed:
cannot.undo.error.contains.nonundoable.changes.message=Following files have changes that cannot be undone:
undo.conflicting.change.confirmation.message=Other files affected by this action have been already changed.
undo.dialog.title=Undo
redo.command.confirmation.text=Redo {0}
redo.confirmation.title=Redo