diff --git a/spec/app/undo-manager-spec.coffee b/spec/app/undo-manager-spec.coffee index cd3fc0c9c..32f141667 100644 --- a/spec/app/undo-manager-spec.coffee +++ b/spec/app/undo-manager-spec.coffee @@ -63,54 +63,100 @@ describe "UndoManager", -> undoManager.redo() expect(buffer.getText()).toContain 'qsport' - describe "transact(fn)", -> - it "causes changes in the transaction to be undone simultaneously", -> - buffer.insert([0, 0], "foo") + describe "transact([fn])", -> + describe "when called with a function", -> + it "causes changes performed within the function's dynamic extent to be undone simultaneously", -> + buffer.insert([0, 0], "foo") - undoManager.transact -> undoManager.transact -> - buffer.insert([1, 2], "111") - buffer.insert([1, 9], "222") + undoManager.transact -> + buffer.insert([1, 2], "111") + buffer.insert([1, 9], "222") - expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {' + expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {' - undoManager.undo() - expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {' - expect(buffer.lineForRow(0)).toContain 'foo' + undoManager.undo() + expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {' + expect(buffer.lineForRow(0)).toContain 'foo' - undoManager.undo() + undoManager.undo() - expect(buffer.lineForRow(0)).not.toContain 'foo' + expect(buffer.lineForRow(0)).not.toContain 'foo' - undoManager.redo() - expect(buffer.lineForRow(0)).toContain 'foo' + undoManager.redo() + expect(buffer.lineForRow(0)).toContain 'foo' - undoManager.redo() - expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {' + undoManager.redo() + expect(buffer.lineForRow(1)).toBe ' 111var 222sort = function(items) {' - undoManager.undo() - expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {' + undoManager.undo() + expect(buffer.lineForRow(1)).toBe ' var sort = function(items) {' - it "does not record empty transactions", -> - buffer.insert([0,0], "foo") - undoManager.transact -> + it "does not record empty transactions", -> + buffer.insert([0,0], "foo") + undoManager.transact -> - undoManager.undo() - expect(buffer.lineForRow(0)).not.toContain("foo") + 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") + it "undoes operations that occured prior to an exception when the transaction is undone", -> + buffer.setText("jumpstreet") + + try + undoManager.transact -> + buffer.insert([0,0], "3") + buffer.insert([0,0], "2") + throw new Error("problem") + buffer.insert([0,0], "2") + catch e + expect(e.message).toBe "problem" + + expect(buffer.lineForRow(0)).toBe "23jumpstreet" + undoManager.undo() + expect(buffer.lineForRow(0)).toBe "jumpstreet" + + describe "when called without a function", -> + beforeEach -> + buffer.setText('') + + it "returns a transaction object that can be committed later", -> + buffer.append('1') + undoManager.transact() + buffer.append('2') + buffer.append('3') + undoManager.commit() + buffer.append('4') + + expect(buffer.getText()).toBe '1234' + undoManager.undo() + expect(buffer.getText()).toBe '123' + undoManager.undo() + expect(buffer.getText()).toBe '1' + undoManager.redo() + expect(buffer.getText()).toBe '123' + + it "returns a transaction object that can be aborted later", -> + buffer.append('1') + buffer.append('2') + + undoManager.transact() + + buffer.append('3') + buffer.append('4') + expect(buffer.getText()).toBe '1234' + + undoManager.abort() + expect(buffer.getText()).toBe '12' + + undoManager.undo() + expect(buffer.getText()).toBe '1' + + undoManager.redo() + expect(buffer.getText()).toBe '12' + + undoManager.redo() + expect(buffer.getText()).toBe '12' - 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", -> @@ -125,7 +171,6 @@ describe "UndoManager", -> undoManager.undo() expect(buffer.lineForRow(0)).toBe "1word" - describe "when an `undo` operation throws an exception", -> it "clears the stack", -> spyOn(console, 'error') diff --git a/src/app/undo-manager.coffee b/src/app/undo-manager.coffee index d3e4b8273..bded883b9 100644 --- a/src/app/undo-manager.coffee +++ b/src/app/undo-manager.coffee @@ -29,19 +29,22 @@ class UndoManager @clear() transact: (fn) -> - safeFn = -> + shouldCommit = not @currentTransaction? + @currentTransaction ?= [] + if fn try fn() - catch e - console.error e.stack + finally + @commit() if shouldCommit - if @currentTransaction - safeFn() - else - @currentTransaction = [] - safeFn() - @undoHistory.push(@currentTransaction) if @currentTransaction?.length - @currentTransaction = null + commit: -> + @undoHistory.push(@currentTransaction) if @currentTransaction?.length + @currentTransaction = null + + abort: -> + @commit() + @undo() + @redoHistory.pop() undo: (editSession) -> try