From bb9c2e1bcb6bf864e888288d68f6dca0246e3e37 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Mon, 12 Nov 2012 15:59:44 -0800 Subject: [PATCH] Handle exceptions thrown during do/undo/redo --- spec/app/undo-manager-spec.coffee | 54 ++++++++++++++++++++++++++++ src/app/undo-manager.coffee | 58 ++++++++++++++++++++++--------- 2 files changed, 95 insertions(+), 17 deletions(-) diff --git a/spec/app/undo-manager-spec.coffee b/spec/app/undo-manager-spec.coffee index 307412a74..d4933f612 100644 --- a/spec/app/undo-manager-spec.coffee +++ b/spec/app/undo-manager-spec.coffee @@ -98,3 +98,57 @@ describe "UndoManager", -> undoManager.undo() expect(buffer.lineForRow(0)).not.toContain("foo") + it "records transactions that occur prior to an exception", -> + spyOn(console, 'error') + buffer.setText("jumpstreet") + undoManager.transact -> + buffer.insert([0,0], "3") + buffer.insert([0,0], "2") + throw new Error("problem") + buffer.insert([0,0], "2") + + expect(console.error).toHaveBeenCalled() + expect(buffer.lineForRow(0)).toBe "23jumpstreet" + undoManager.undo() + expect(buffer.lineForRow(0)).toBe "jumpstreet" + + describe "when a `do` operation throws an exception", -> + it "clears the stack", -> + spyOn(console, 'error') + buffer.setText("word") + class FailingOperation + do: -> throw new Error("I'm a bad do operation") + + buffer.insert([0,0], "1") + undoManager.pushOperation(new FailingOperation()) + expect(console.error).toHaveBeenCalled() + undoManager.undo() + expect(buffer.lineForRow(0)).toBe "1word" + + + describe "when an `undo` operation throws an exception", -> + it "clears the stack", -> + spyOn(console, 'error') + buffer.setText("word") + class FailingOperation + undo: -> throw new Error("I'm a bad undo operation") + + buffer.insert([0,0], "1") + undoManager.pushOperation(new FailingOperation()) + undoManager.undo() + expect(console.error).toHaveBeenCalled() + expect(buffer.lineForRow(0)).toBe "1word" + + describe "when an `redo` operation throws an exception", -> + it "clears the stack", -> + spyOn(console, 'error') + buffer.setText("word") + class FailingOperation + redo: -> throw new Error("I'm a bad undo operation") + + buffer.insert([0,0], "1") + undoManager.pushOperation(new FailingOperation()) + undoManager.undo() + undoManager.redo() + expect(console.error).toHaveBeenCalled() + expect(buffer.lineForRow(0)).toBe "1word" \ No newline at end of file diff --git a/src/app/undo-manager.coffee b/src/app/undo-manager.coffee index 21760ef62..d3e4b8273 100644 --- a/src/app/undo-manager.coffee +++ b/src/app/undo-manager.coffee @@ -8,7 +8,10 @@ class UndoManager currentTransaction: null constructor: -> - @startBatchCallCount = 0 + @clear() + + clear: -> + @currentTransaction = null @undoHistory = [] @redoHistory = [] @@ -18,29 +21,50 @@ class UndoManager else @undoHistory.push([operation]) @redoHistory = [] - operation.do?(editSession) + + try + operation.do?(editSession) + catch e + console.error e.stack + @clear() transact: (fn) -> + safeFn = -> + try + fn() + catch e + console.error e.stack + if @currentTransaction - fn() + safeFn() else @currentTransaction = [] - fn() - @undoHistory.push(@currentTransaction) if @currentTransaction.length + safeFn() + @undoHistory.push(@currentTransaction) if @currentTransaction?.length @currentTransaction = null undo: (editSession) -> - if batch = @undoHistory.pop() - opsInReverse = new Array(batch...) - opsInReverse.reverse() - op.undo?(editSession) for op in opsInReverse - @redoHistory.push batch - batch.oldSelectionRanges + try + if batch = @undoHistory.pop() + opsInReverse = new Array(batch...) + opsInReverse.reverse() + op.undo?(editSession) for op in opsInReverse + + @redoHistory.push batch + batch.oldSelectionRanges + catch e + console.error e.stack + @clear() redo: (editSession) -> - if batch = @redoHistory.pop() - for op in batch - op.do?(editSession) - op.redo?(editSession) - @undoHistory.push(batch) - batch.newSelectionRanges + try + if batch = @redoHistory.pop() + for op in batch + op.do?(editSession) + op.redo?(editSession) + + @undoHistory.push(batch) + batch.newSelectionRanges + catch e + console.error e.stack + @clear()