:lipstick: Flatten the structure of editor-spec

Previously there were 2 top-level describes:

"with an initial line option"
"with default options"

The default options describe contained almost all the specs, so this
commit just creates the editor again in a single spec about the
initialLine option and peels off a layer.

--- spec/editor-spec.coffee
+++ spec/editor-spec.coffee
@@ -6,2783 +6,2780 @@
   convertToHardTabs = (buffer) ->
     buffer.setText(buffer.getText().replace(/[ ]{2}/g, "\t"))

-  describe "with an initial line option", ->
-    beforeEach ->
+  beforeEach ->
+    editor = atom.project.openSync('sample.js', autoIndent: false)
+    buffer = editor.buffer
+    lineLengths = buffer.getLines().map (line) -> line.length
+
+    waitsForPromise ->
+      atom.packages.activatePackage('language-javascript')
+
+  describe "when the editor is deserialized", ->
+    it "restores selections and folds based on markers in the buffer", ->
+      editor.setSelectedBufferRange([[1, 2], [3, 4]])
+      editor.addSelectionForBufferRange([[5, 6], [7, 5]], isReversed: true)
+      editor.foldBufferRow(4)
+      expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+
+      editor2 = editor.testSerialization()
+
+      expect(editor2.id).toBe editor.id
+      expect(editor2.getBuffer().getPath()).toBe editor.getBuffer().getPath()
+      expect(editor2.getSelectedBufferRanges()).toEqual [[[1, 2], [3, 4]], [[5, 6], [7, 5]]]
+      expect(editor2.getSelection(1).isReversed()).toBeTruthy()
+      expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
+      editor2.destroy()
+
+  describe "when the editor is constructed with an initialLine option", ->
+    it "and positions the cursor on the specified line", ->
       editor = atom.project.openSync('sample.js', initialLine: 2)
       buffer = editor.buffer
-
-    it "opens the file and positions the cursor on line 2", ->
       expect(editor.getCursor().getBufferPosition().row).toEqual 2

-  describe "with default options", ->
-    beforeEach ->
-      editor = atom.project.openSync('sample.js', autoIndent: false)
-      buffer = editor.buffer
-      lineLengths = buffer.getLines().map (line) -> line.length
-
-      waitsForPromise ->
-        atom.packages.activatePackage('language-javascript')
-
-    describe "when the editor is deserialized", ->
-      it "restores selections and folds based on markers in the buffer", ->
-        editor.setSelectedBufferRange([[1, 2], [3, 4]])
-        editor.addSelectionForBufferRange([[5, 6], [7, 5]], isReversed: true)
-        editor.foldBufferRow(4)
-        expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
-
-        editor2 = editor.testSerialization()
-
-        expect(editor2.id).toBe editor.id
-        expect(editor2.getBuffer().getPath()).toBe editor.getBuffer().getPath()
-        expect(editor2.getSelectedBufferRanges()).toEqual [[[1, 2], [3, 4]], [[5, 6], [7, 5]]]
-        expect(editor2.getSelection(1).isReversed()).toBeTruthy()
-        expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
-        editor2.destroy()
-
-    describe ".copy()", ->
-      it "returns a different edit session with the same initial state", ->
-        editor.setSelectedBufferRange([[1, 2], [3, 4]])
-        editor.addSelectionForBufferRange([[5, 6], [7, 8]], isReversed: true)
-        editor.foldBufferRow(4)
-        expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
-
-        editor2 = editor.copy()
-        expect(editor2.id).not.toBe editor.id
-        expect(editor2.getSelectedBufferRanges()).toEqual editor.getSelectedBufferRanges()
-        expect(editor2.getSelection(1).isReversed()).toBeTruthy()
-        expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
-
-        # editor2 can now diverge from its origin edit session
-        editor2.getSelection().setBufferRange([[2, 1], [4, 3]])
-        expect(editor2.getSelectedBufferRanges()).not.toEqual editor.getSelectedBufferRanges()
-        editor2.unfoldBufferRow(4)
-        expect(editor2.isFoldedAtBufferRow(4)).not.toBe editor.isFoldedAtBufferRow(4)
-
-    describe "config defaults", ->
-      it "uses the `editor.tabLength`, `editor.softWrap`, and `editor.softTabs` config values", ->
-        atom.config.set('editor.tabLength', 4)
-        atom.config.set('editor.softWrap', true)
-        atom.config.set('editor.softTabs', false)
-        editor1 = atom.project.openSync('a')
-        expect(editor1.getTabLength()).toBe 4
-        expect(editor1.getSoftWrap()).toBe true
-        expect(editor1.getSoftTabs()).toBe false
-
-        atom.config.set('editor.tabLength', 100)
-        atom.config.set('editor.softWrap', false)
-        atom.config.set('editor.softTabs', true)
-        editor2 = atom.project.openSync('b')
-        expect(editor2.getTabLength()).toBe 100
-        expect(editor2.getSoftWrap()).toBe false
-        expect(editor2.getSoftTabs()).toBe true
-
-    describe "title", ->
-      describe ".getTitle()", ->
-        it "uses the basename of the buffer's path as its title, or 'untitled' if the path is undefined", ->
-          expect(editor.getTitle()).toBe 'sample.js'
-          buffer.setPath(undefined)
-          expect(editor.getTitle()).toBe 'untitled'
-
-      describe ".getLongTitle()", ->
-        it "appends the name of the containing directory to the basename of the file", ->
-          expect(editor.getLongTitle()).toBe 'sample.js - fixtures'
-          buffer.setPath(undefined)
-          expect(editor.getLongTitle()).toBe 'untitled'
-
-      it "emits 'title-changed' events when the underlying buffer path", ->
-        titleChangedHandler = jasmine.createSpy("titleChangedHandler")
-        editor.on 'title-changed', titleChangedHandler
-
-        buffer.setPath('/foo/bar/baz.txt')
+  describe ".copy()", ->
+    it "returns a different edit session with the same initial state", ->
+      editor.setSelectedBufferRange([[1, 2], [3, 4]])
+      editor.addSelectionForBufferRange([[5, 6], [7, 8]], isReversed: true)
+      editor.foldBufferRow(4)
+      expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+
+      editor2 = editor.copy()
+      expect(editor2.id).not.toBe editor.id
+      expect(editor2.getSelectedBufferRanges()).toEqual editor.getSelectedBufferRanges()
+      expect(editor2.getSelection(1).isReversed()).toBeTruthy()
+      expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
+
+      # editor2 can now diverge from its origin edit session
+      editor2.getSelection().setBufferRange([[2, 1], [4, 3]])
+      expect(editor2.getSelectedBufferRanges()).not.toEqual editor.getSelectedBufferRanges()
+      editor2.unfoldBufferRow(4)
+      expect(editor2.isFoldedAtBufferRow(4)).not.toBe editor.isFoldedAtBufferRow(4)
+
+  describe "config defaults", ->
+    it "uses the `editor.tabLength`, `editor.softWrap`, and `editor.softTabs` config values", ->
+      atom.config.set('editor.tabLength', 4)
+      atom.config.set('editor.softWrap', true)
+      atom.config.set('editor.softTabs', false)
+      editor1 = atom.project.openSync('a')
+      expect(editor1.getTabLength()).toBe 4
+      expect(editor1.getSoftWrap()).toBe true
+      expect(editor1.getSoftTabs()).toBe false
+
+      atom.config.set('editor.tabLength', 100)
+      atom.config.set('editor.softWrap', false)
+      atom.config.set('editor.softTabs', true)
+      editor2 = atom.project.openSync('b')
+      expect(editor2.getTabLength()).toBe 100
+      expect(editor2.getSoftWrap()).toBe false
+      expect(editor2.getSoftTabs()).toBe true
+
+  describe "title", ->
+    describe ".getTitle()", ->
+      it "uses the basename of the buffer's path as its title, or 'untitled' if the path is undefined", ->
+        expect(editor.getTitle()).toBe 'sample.js'
         buffer.setPath(undefined)
-        expect(titleChangedHandler.callCount).toBe 2
-
-    describe "cursor", ->
-      describe ".getCursor()", ->
-        it "returns the most recently created cursor", ->
-          editor.addCursorAtScreenPosition([1, 0])
-          lastCursor = editor.addCursorAtScreenPosition([2, 0])
-          expect(editor.getCursor()).toBe lastCursor
-
-      describe "when the cursor moves", ->
-        it "clears a goal column established by vertical movement", ->
-          editor.setText('b')
-          editor.setCursorBufferPosition([0,0])
-          editor.insertNewline()
+        expect(editor.getTitle()).toBe 'untitled'
+
+    describe ".getLongTitle()", ->
+      it "appends the name of the containing directory to the basename of the file", ->
+        expect(editor.getLongTitle()).toBe 'sample.js - fixtures'
+        buffer.setPath(undefined)
+        expect(editor.getLongTitle()).toBe 'untitled'
+
+    it "emits 'title-changed' events when the underlying buffer path", ->
+      titleChangedHandler = jasmine.createSpy("titleChangedHandler")
+      editor.on 'title-changed', titleChangedHandler
+
+      buffer.setPath('/foo/bar/baz.txt')
+      buffer.setPath(undefined)
+      expect(titleChangedHandler.callCount).toBe 2
+
+  describe "cursor", ->
+    describe ".getCursor()", ->
+      it "returns the most recently created cursor", ->
+        editor.addCursorAtScreenPosition([1, 0])
+        lastCursor = editor.addCursorAtScreenPosition([2, 0])
+        expect(editor.getCursor()).toBe lastCursor
+
+    describe "when the cursor moves", ->
+      it "clears a goal column established by vertical movement", ->
+        editor.setText('b')
+        editor.setCursorBufferPosition([0,0])
+        editor.insertNewline()
+        editor.moveCursorUp()
+        editor.insertText('a')
+        editor.moveCursorDown()
+        expect(editor.getCursorBufferPosition()).toEqual [1, 1]
+
+    describe ".setCursorScreenPosition(screenPosition)", ->
+      it "clears a goal column established by vertical movement", ->
+        # set a goal column by moving down
+        editor.setCursorScreenPosition(row: 3, column: lineLengths[3])
+        editor.moveCursorDown()
+        expect(editor.getCursorScreenPosition().column).not.toBe 6
+
+        # clear the goal column by explicitly setting the cursor position
+        editor.setCursorScreenPosition([4,6])
+        expect(editor.getCursorScreenPosition().column).toBe 6
+
+        editor.moveCursorDown()
+        expect(editor.getCursorScreenPosition().column).toBe 6
+
+      it "merges multiple cursors", ->
+        editor.setCursorScreenPosition([0, 0])
+        editor.addCursorAtScreenPosition([0, 1])
+        [cursor1, cursor2] = editor.getCursors()
+        editor.setCursorScreenPosition([4, 7])
+        expect(editor.getCursors().length).toBe 1
+        expect(editor.getCursors()).toEqual [cursor1]
+        expect(editor.getCursorScreenPosition()).toEqual [4, 7]
+
+      describe "when soft-wrap is enabled and code is folded", ->
+        beforeEach ->
+          editor.setSoftWrap(true)
+          editor.setEditorWidthInChars(50)
+          editor.createFold(2, 3)
+
+        it "positions the cursor at the buffer position that corresponds to the given screen position", ->
+          editor.setCursorScreenPosition([9, 0])
+          expect(editor.getCursorBufferPosition()).toEqual [8, 11]
+
+    describe ".moveCursorUp()", ->
+      it "moves the cursor up", ->
+        editor.setCursorScreenPosition([2, 2])
+        editor.moveCursorUp()
+        expect(editor.getCursorScreenPosition()).toEqual [1, 2]
+
+      it "retains the goal column across lines of differing length", ->
+        expect(lineLengths[6]).toBeGreaterThan(32)
+        editor.setCursorScreenPosition(row: 6, column: 32)
+
+        editor.moveCursorUp()
+        expect(editor.getCursorScreenPosition().column).toBe lineLengths[5]
+
+        editor.moveCursorUp()
+        expect(editor.getCursorScreenPosition().column).toBe lineLengths[4]
+
+        editor.moveCursorUp()
+        expect(editor.getCursorScreenPosition().column).toBe 32
+
+      describe "when the cursor is on the first line", ->
+        it "moves the cursor to the beginning of the line, but retains the goal column", ->
+          editor.setCursorScreenPosition([0, 4])
           editor.moveCursorUp()
-          editor.insertText('a')
+          expect(editor.getCursorScreenPosition()).toEqual([0, 0])
+
           editor.moveCursorDown()
-          expect(editor.getCursorBufferPosition()).toEqual [1, 1]
-
-      describe ".setCursorScreenPosition(screenPosition)", ->
-        it "clears a goal column established by vertical movement", ->
-          # set a goal column by moving down
-          editor.setCursorScreenPosition(row: 3, column: lineLengths[3])
+          expect(editor.getCursorScreenPosition()).toEqual([1, 4])
+
+      describe "when there is a selection", ->
+        beforeEach ->
+          editor.setSelectedBufferRange([[4, 9],[5, 10]])
+
+        it "moves above the selection", ->
+          cursor = editor.getCursor()
+          editor.moveCursorUp()
+          expect(cursor.getBufferPosition()).toEqual [3, 9]
+
+      it "merges cursors when they overlap", ->
+        editor.addCursorAtScreenPosition([1, 0])
+        [cursor1, cursor2] = editor.getCursors()
+
+        editor.moveCursorUp()
+        expect(editor.getCursors()).toEqual [cursor1]
+        expect(cursor1.getBufferPosition()).toEqual [0,0]
+
+    describe ".moveCursorDown()", ->
+      it "moves the cursor down", ->
+        editor.setCursorScreenPosition([2, 2])
+        editor.moveCursorDown()
+        expect(editor.getCursorScreenPosition()).toEqual [3, 2]
+
+      it "retains the goal column across lines of differing length", ->
+        editor.setCursorScreenPosition(row: 3, column: lineLengths[3])
+
+        editor.moveCursorDown()
+        expect(editor.getCursorScreenPosition().column).toBe lineLengths[4]
+
+        editor.moveCursorDown()
+        expect(editor.getCursorScreenPosition().column).toBe lineLengths[5]
+
+        editor.moveCursorDown()
+        expect(editor.getCursorScreenPosition().column).toBe lineLengths[3]
+
+      describe "when the cursor is on the last line", ->
+        it "moves the cursor to the end of line, but retains the goal column when moving back up", ->
+          lastLineIndex = buffer.getLines().length - 1
+          lastLine = buffer.lineForRow(lastLineIndex)
+          expect(lastLine.length).toBeGreaterThan(0)
+
+          editor.setCursorScreenPosition(row: lastLineIndex, column: editor.getTabLength())
           editor.moveCursorDown()
-          expect(editor.getCursorScreenPosition().column).not.toBe 6
-
-          # clear the goal column by explicitly setting the cursor position
-          editor.setCursorScreenPosition([4,6])
-          expect(editor.getCursorScreenPosition().column).toBe 6
-
+          expect(editor.getCursorScreenPosition()).toEqual(row: lastLineIndex, column: lastLine.length)
+
+          editor.moveCursorUp()
+          expect(editor.getCursorScreenPosition().column).toBe editor.getTabLength()
+
+        it "retains a goal column of 0 when moving back up", ->
+          lastLineIndex = buffer.getLines().length - 1
+          lastLine = buffer.lineForRow(lastLineIndex)
+          expect(lastLine.length).toBeGreaterThan(0)
+
+          editor.setCursorScreenPosition(row: lastLineIndex, column: 0)
           editor.moveCursorDown()
-          expect(editor.getCursorScreenPosition().column).toBe 6
-
-        it "merges multiple cursors", ->
-          editor.setCursorScreenPosition([0, 0])
-          editor.addCursorAtScreenPosition([0, 1])
-          [cursor1, cursor2] = editor.getCursors()
-          editor.setCursorScreenPosition([4, 7])
-          expect(editor.getCursors().length).toBe 1
-          expect(editor.getCursors()).toEqual [cursor1]
-          expect(editor.getCursorScreenPosition()).toEqual [4, 7]
-
-        describe "when soft-wrap is enabled and code is folded", ->
-          beforeEach ->
-            editor.setSoftWrap(true)
-            editor.setEditorWidthInChars(50)
-            editor.createFold(2, 3)
-
-          it "positions the cursor at the buffer position that corresponds to the given screen position", ->
-            editor.setCursorScreenPosition([9, 0])
-            expect(editor.getCursorBufferPosition()).toEqual [8, 11]
-
-      describe ".moveCursorUp()", ->
-        it "moves the cursor up", ->
-          editor.setCursorScreenPosition([2, 2])
           editor.moveCursorUp()
-          expect(editor.getCursorScreenPosition()).toEqual [1, 2]
-
-        it "retains the goal column across lines of differing length", ->
-          expect(lineLengths[6]).toBeGreaterThan(32)
-          editor.setCursorScreenPosition(row: 6, column: 32)
-
-          editor.moveCursorUp()
-          expect(editor.getCursorScreenPosition().column).toBe lineLengths[5]
-
-          editor.moveCursorUp()
-          expect(editor.getCursorScreenPosition().column).toBe lineLengths[4]
-
-          editor.moveCursorUp()
-          expect(editor.getCursorScreenPosition().column).toBe 32
+          expect(editor.getCursorScreenPosition().column).toBe 0
+
+      describe "when there is a selection", ->
+        beforeEach ->
+          editor.setSelectedBufferRange([[4, 9],[5, 10]])
+
+        it "moves below the selection", ->
+          cursor = editor.getCursor()
+          editor.moveCursorDown()
+          expect(cursor.getBufferPosition()).toEqual [6, 10]
+
+      it "merges cursors when they overlap", ->
+        editor.setCursorScreenPosition([12, 2])
+        editor.addCursorAtScreenPosition([11, 2])
+        [cursor1, cursor2] = editor.getCursors()
+
+        editor.moveCursorDown()
+        expect(editor.getCursors()).toEqual [cursor1]
+        expect(cursor1.getBufferPosition()).toEqual [12,2]
+
+    describe ".moveCursorLeft()", ->
+      it "moves the cursor by one column to the left", ->
+        editor.setCursorScreenPosition([1, 8])
+        editor.moveCursorLeft()
+        expect(editor.getCursorScreenPosition()).toEqual [1, 7]
+
+      describe "when the cursor is in the first column", ->
+        describe "when there is a previous line", ->
+          it "wraps to the end of the previous line", ->
+            editor.setCursorScreenPosition(row: 1, column: 0)
+            editor.moveCursorLeft()
+            expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: buffer.lineForRow(0).length)

         describe "when the cursor is on the first line", ->
-          it "moves the cursor to the beginning of the line, but retains the goal column", ->
-            editor.setCursorScreenPosition([0, 4])
-            editor.moveCursorUp()
-            expect(editor.getCursorScreenPosition()).toEqual([0, 0])
-
-            editor.moveCursorDown()
-            expect(editor.getCursorScreenPosition()).toEqual([1, 4])
-
-        describe "when there is a selection", ->
-          beforeEach ->
-            editor.setSelectedBufferRange([[4, 9],[5, 10]])
-
-          it "moves above the selection", ->
-            cursor = editor.getCursor()
-            editor.moveCursorUp()
-            expect(cursor.getBufferPosition()).toEqual [3, 9]
-
-        it "merges cursors when they overlap", ->
-          editor.addCursorAtScreenPosition([1, 0])
-          [cursor1, cursor2] = editor.getCursors()
-
-          editor.moveCursorUp()
-          expect(editor.getCursors()).toEqual [cursor1]
-          expect(cursor1.getBufferPosition()).toEqual [0,0]
-
-      describe ".moveCursorDown()", ->
-        it "moves the cursor down", ->
-          editor.setCursorScreenPosition([2, 2])
-          editor.moveCursorDown()
-          expect(editor.getCursorScreenPosition()).toEqual [3, 2]
-
-        it "retains the goal column across lines of differing length", ->
-          editor.setCursorScreenPosition(row: 3, column: lineLengths[3])
-
-          editor.moveCursorDown()
-          expect(editor.getCursorScreenPosition().column).toBe lineLengths[4]
-
-          editor.moveCursorDown()
-          expect(editor.getCursorScreenPosition().column).toBe lineLengths[5]
-
-          editor.moveCursorDown()
-          expect(editor.getCursorScreenPosition().column).toBe lineLengths[3]
+          it "remains in the same position (0,0)", ->
+            editor.setCursorScreenPosition(row: 0, column: 0)
+            editor.moveCursorLeft()
+            expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
+
+      describe "when softTabs is enabled and the cursor is preceded by leading whitespace", ->
+        it "skips tabLength worth of whitespace at a time", ->
+          editor.setCursorBufferPosition([5, 6])
+
+          editor.moveCursorLeft()
+          expect(editor.getCursorBufferPosition()).toEqual [5, 4]
+
+      describe "when there is a selection", ->
+        beforeEach ->
+          editor.setSelectedBufferRange([[5, 22],[5, 27]])
+
+        it "moves to the left of the selection", ->
+          cursor = editor.getCursor()
+          editor.moveCursorLeft()
+          expect(cursor.getBufferPosition()).toEqual [5, 22]
+
+          editor.moveCursorLeft()
+          expect(cursor.getBufferPosition()).toEqual [5, 21]
+
+      it "merges cursors when they overlap", ->
+        editor.setCursorScreenPosition([0, 0])
+        editor.addCursorAtScreenPosition([0, 1])
+
+        [cursor1, cursor2] = editor.getCursors()
+        editor.moveCursorLeft()
+        expect(editor.getCursors()).toEqual [cursor1]
+        expect(cursor1.getBufferPosition()).toEqual [0,0]
+
+    describe ".moveCursorRight()", ->
+      it "moves the cursor by one column to the right", ->
+        editor.setCursorScreenPosition([3, 3])
+        editor.moveCursorRight()
+        expect(editor.getCursorScreenPosition()).toEqual [3, 4]
+
+      describe "when the cursor is on the last column of a line", ->
+        describe "when there is a subsequent line", ->
+          it "wraps to the beginning of the next line", ->
+            editor.setCursorScreenPosition([0, buffer.lineForRow(0).length])
+            editor.moveCursorRight()
+            expect(editor.getCursorScreenPosition()).toEqual [1, 0]

         describe "when the cursor is on the last line", ->
-          it "moves the cursor to the end of line, but retains the goal column when moving back up", ->
+          it "remains in the same position", ->
             lastLineIndex = buffer.getLines().length - 1
             lastLine = buffer.lineForRow(lastLineIndex)
             expect(lastLine.length).toBeGreaterThan(0)

-            editor.setCursorScreenPosition(row: lastLineIndex, column: editor.getTabLength())
-            editor.moveCursorDown()
-            expect(editor.getCursorScreenPosition()).toEqual(row: lastLineIndex, column: lastLine.length)
-
-            editor.moveCursorUp()
-            expect(editor.getCursorScreenPosition().column).toBe editor.getTabLength()
-
-          it "retains a goal column of 0 when moving back up", ->
-            lastLineIndex = buffer.getLines().length - 1
-            lastLine = buffer.lineForRow(lastLineIndex)
-            expect(lastLine.length).toBeGreaterThan(0)
-
-            editor.setCursorScreenPosition(row: lastLineIndex, column: 0)
-            editor.moveCursorDown()
-            editor.moveCursorUp()
-            expect(editor.getCursorScreenPosition().column).toBe 0
-
-        describe "when there is a selection", ->
-          beforeEach ->
-            editor.setSelectedBufferRange([[4, 9],[5, 10]])
-
-          it "moves below the selection", ->
-            cursor = editor.getCursor()
-            editor.moveCursorDown()
-            expect(cursor.getBufferPosition()).toEqual [6, 10]
-
-        it "merges cursors when they overlap", ->
-          editor.setCursorScreenPosition([12, 2])
-          editor.addCursorAtScreenPosition([11, 2])
-          [cursor1, cursor2] = editor.getCursors()
-
-          editor.moveCursorDown()
-          expect(editor.getCursors()).toEqual [cursor1]
-          expect(cursor1.getBufferPosition()).toEqual [12,2]
-
-      describe ".moveCursorLeft()", ->
-        it "moves the cursor by one column to the left", ->
-          editor.setCursorScreenPosition([1, 8])
-          editor.moveCursorLeft()
-          expect(editor.getCursorScreenPosition()).toEqual [1, 7]
-
-        describe "when the cursor is in the first column", ->
-          describe "when there is a previous line", ->
-            it "wraps to the end of the previous line", ->
-              editor.setCursorScreenPosition(row: 1, column: 0)
-              editor.moveCursorLeft()
-              expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: buffer.lineForRow(0).length)
-
-          describe "when the cursor is on the first line", ->
-            it "remains in the same position (0,0)", ->
-              editor.setCursorScreenPosition(row: 0, column: 0)
-              editor.moveCursorLeft()
-              expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
-
-        describe "when softTabs is enabled and the cursor is preceded by leading whitespace", ->
-          it "skips tabLength worth of whitespace at a time", ->
-            editor.setCursorBufferPosition([5, 6])
-
-            editor.moveCursorLeft()
-            expect(editor.getCursorBufferPosition()).toEqual [5, 4]
-
-        describe "when there is a selection", ->
-          beforeEach ->
-            editor.setSelectedBufferRange([[5, 22],[5, 27]])
-
-          it "moves to the left of the selection", ->
-            cursor = editor.getCursor()
-            editor.moveCursorLeft()
-            expect(cursor.getBufferPosition()).toEqual [5, 22]
-
-            editor.moveCursorLeft()
-            expect(cursor.getBufferPosition()).toEqual [5, 21]
-
-        it "merges cursors when they overlap", ->
-          editor.setCursorScreenPosition([0, 0])
-          editor.addCursorAtScreenPosition([0, 1])
-
-          [cursor1, cursor2] = editor.getCursors()
-          editor.moveCursorLeft()
-          expect(editor.getCursors()).toEqual [cursor1]
-          expect(cursor1.getBufferPosition()).toEqual [0,0]
-
-      describe ".moveCursorRight()", ->
-        it "moves the cursor by one column to the right", ->
-          editor.setCursorScreenPosition([3, 3])
+            lastPosition = { row: lastLineIndex, column: lastLine.length }
+            editor.setCursorScreenPosition(lastPosition)
+            editor.moveCursorRight()
+
+            expect(editor.getCursorScreenPosition()).toEqual(lastPosition)
+
+      describe "when there is a selection", ->
+        beforeEach ->
+          editor.setSelectedBufferRange([[5, 22],[5, 27]])
+
+        it "moves to the left of the selection", ->
+          cursor = editor.getCursor()
           editor.moveCursorRight()
-          expect(editor.getCursorScreenPosition()).toEqual [3, 4]
-
-        describe "when the cursor is on the last column of a line", ->
-          describe "when there is a subsequent line", ->
-            it "wraps to the beginning of the next line", ->
-              editor.setCursorScreenPosition([0, buffer.lineForRow(0).length])
-              editor.moveCursorRight()
-              expect(editor.getCursorScreenPosition()).toEqual [1, 0]
-
-          describe "when the cursor is on the last line", ->
-            it "remains in the same position", ->
-              lastLineIndex = buffer.getLines().length - 1
-              lastLine = buffer.lineForRow(lastLineIndex)
-              expect(lastLine.length).toBeGreaterThan(0)
-
-              lastPosition = { row: lastLineIndex, column: lastLine.length }
-              editor.setCursorScreenPosition(lastPosition)
-              editor.moveCursorRight()
-
-              expect(editor.getCursorScreenPosition()).toEqual(lastPosition)
-
-        describe "when there is a selection", ->
-          beforeEach ->
-            editor.setSelectedBufferRange([[5, 22],[5, 27]])
-
-          it "moves to the left of the selection", ->
-            cursor = editor.getCursor()
-            editor.moveCursorRight()
-            expect(cursor.getBufferPosition()).toEqual [5, 27]
-
-            editor.moveCursorRight()
-            expect(cursor.getBufferPosition()).toEqual [5, 28]
-
-        it "merges cursors when they overlap", ->
-          editor.setCursorScreenPosition([12, 2])
-          editor.addCursorAtScreenPosition([12, 1])
-          [cursor1, cursor2] = editor.getCursors()
+          expect(cursor.getBufferPosition()).toEqual [5, 27]

           editor.moveCursorRight()
-          expect(editor.getCursors()).toEqual [cursor1]
-          expect(cursor1.getBufferPosition()).toEqual [12,2]
-
-      describe ".moveCursorToTop()", ->
-        it "moves the cursor to the top of the buffer", ->
-          editor.setCursorScreenPosition [11,1]
-          editor.addCursorAtScreenPosition [12,0]
-          editor.moveCursorToTop()
-          expect(editor.getCursors().length).toBe 1
-          expect(editor.getCursorBufferPosition()).toEqual [0,0]
-
-      describe ".moveCursorToBottom()", ->
-        it "moves the cusor to the bottom of the buffer", ->
-          editor.setCursorScreenPosition [0,0]
-          editor.addCursorAtScreenPosition [1,0]
-          editor.moveCursorToBottom()
-          expect(editor.getCursors().length).toBe 1
-          expect(editor.getCursorBufferPosition()).toEqual [12,2]
-
-      describe ".moveCursorToBeginningOfScreenLine()", ->
-        describe "when soft wrap is on", ->
-          it "moves cursor to the beginning of the screen line", ->
-            editor.setSoftWrap(true)
-            editor.setEditorWidthInChars(10)
-            editor.setCursorScreenPosition([1, 2])
-            editor.moveCursorToBeginningOfScreenLine()
-            cursor = editor.getCursor()
-            expect(cursor.getScreenPosition()).toEqual [1, 0]
-
-        describe "when soft wrap is off", ->
-          it "moves cursor to the beginning of then line", ->
-            editor.setCursorScreenPosition [0,5]
-            editor.addCursorAtScreenPosition [1,7]
-            editor.moveCursorToBeginningOfScreenLine()
-            expect(editor.getCursors().length).toBe 2
-            [cursor1, cursor2] = editor.getCursors()
-            expect(cursor1.getBufferPosition()).toEqual [0,0]
-            expect(cursor2.getBufferPosition()).toEqual [1,0]
-
-      describe ".moveCursorToEndOfScreenLine()", ->
-        describe "when soft wrap is on", ->
-          it "moves cursor to the beginning of the screen line", ->
-            editor.setSoftWrap(true)
-            editor.setEditorWidthInChars(10)
-            editor.setCursorScreenPosition([1, 2])
-            editor.moveCursorToEndOfScreenLine()
-            cursor = editor.getCursor()
-            expect(cursor.getScreenPosition()).toEqual [1, 9]
-
-        describe "when soft wrap is off", ->
-          it "moves cursor to the end of line", ->
-            editor.setCursorScreenPosition [0,0]
-            editor.addCursorAtScreenPosition [1,0]
-            editor.moveCursorToEndOfScreenLine()
-            expect(editor.getCursors().length).toBe 2
-            [cursor1, cursor2] = editor.getCursors()
-            expect(cursor1.getBufferPosition()).toEqual [0,29]
-            expect(cursor2.getBufferPosition()).toEqual [1,30]
-
-      describe ".moveCursorToBeginningOfLine()", ->
-        it "moves cursor to the beginning of the buffer line", ->
+          expect(cursor.getBufferPosition()).toEqual [5, 28]
+
+      it "merges cursors when they overlap", ->
+        editor.setCursorScreenPosition([12, 2])
+        editor.addCursorAtScreenPosition([12, 1])
+        [cursor1, cursor2] = editor.getCursors()
+
+        editor.moveCursorRight()
+        expect(editor.getCursors()).toEqual [cursor1]
+        expect(cursor1.getBufferPosition()).toEqual [12,2]
+
+    describe ".moveCursorToTop()", ->
+      it "moves the cursor to the top of the buffer", ->
+        editor.setCursorScreenPosition [11,1]
+        editor.addCursorAtScreenPosition [12,0]
+        editor.moveCursorToTop()
+        expect(editor.getCursors().length).toBe 1
+        expect(editor.getCursorBufferPosition()).toEqual [0,0]
+
+    describe ".moveCursorToBottom()", ->
+      it "moves the cusor to the bottom of the buffer", ->
+        editor.setCursorScreenPosition [0,0]
+        editor.addCursorAtScreenPosition [1,0]
+        editor.moveCursorToBottom()
+        expect(editor.getCursors().length).toBe 1
+        expect(editor.getCursorBufferPosition()).toEqual [12,2]
+
+    describe ".moveCursorToBeginningOfScreenLine()", ->
+      describe "when soft wrap is on", ->
+        it "moves cursor to the beginning of the screen line", ->
           editor.setSoftWrap(true)
           editor.setEditorWidthInChars(10)
           editor.setCursorScreenPosition([1, 2])
-          editor.moveCursorToBeginningOfLine()
+          editor.moveCursorToBeginningOfScreenLine()
           cursor = editor.getCursor()
-          expect(cursor.getScreenPosition()).toEqual [0, 0]
-
-      describe ".moveCursorToEndOfLine()", ->
-        it "moves cursor to the end of the buffer line", ->
+          expect(cursor.getScreenPosition()).toEqual [1, 0]
+
+      describe "when soft wrap is off", ->
+        it "moves cursor to the beginning of then line", ->
+          editor.setCursorScreenPosition [0,5]
+          editor.addCursorAtScreenPosition [1,7]
+          editor.moveCursorToBeginningOfScreenLine()
+          expect(editor.getCursors().length).toBe 2
+          [cursor1, cursor2] = editor.getCursors()
+          expect(cursor1.getBufferPosition()).toEqual [0,0]
+          expect(cursor2.getBufferPosition()).toEqual [1,0]
+
+    describe ".moveCursorToEndOfScreenLine()", ->
+      describe "when soft wrap is on", ->
+        it "moves cursor to the beginning of the screen line", ->
           editor.setSoftWrap(true)
           editor.setEditorWidthInChars(10)
-          editor.setCursorScreenPosition([0, 2])
-          editor.moveCursorToEndOfLine()
+          editor.setCursorScreenPosition([1, 2])
+          editor.moveCursorToEndOfScreenLine()
           cursor = editor.getCursor()
-          expect(cursor.getScreenPosition()).toEqual [3, 4]
-
-      describe ".moveCursorToFirstCharacterOfLine()", ->
-        describe "when soft wrap is on", ->
-          it "moves to the first character of the current screen line or the beginning of the screen line if it's already on the first character", ->
-            editor.setSoftWrap(true)
-            editor.setEditorWidthInChars(10)
-            editor.setCursorScreenPosition [2,5]
-            editor.addCursorAtScreenPosition [8,7]
-
-            editor.moveCursorToFirstCharacterOfLine()
-            [cursor1, cursor2] = editor.getCursors()
-            expect(cursor1.getScreenPosition()).toEqual [2,0]
-            expect(cursor2.getScreenPosition()).toEqual [8,4]
-
-            editor.moveCursorToFirstCharacterOfLine()
-            expect(cursor1.getScreenPosition()).toEqual [2,0]
-            expect(cursor2.getScreenPosition()).toEqual [8,0]
-
-        describe "when soft wrap is off", ->
-          it "moves to the first character of the current line or the beginning of the line if it's already on the first character", ->
-            editor.setCursorScreenPosition [0,5]
-            editor.addCursorAtScreenPosition [1,7]
-
-            editor.moveCursorToFirstCharacterOfLine()
-            [cursor1, cursor2] = editor.getCursors()
-            expect(cursor1.getBufferPosition()).toEqual [0,0]
-            expect(cursor2.getBufferPosition()).toEqual [1,2]
-
-            editor.moveCursorToFirstCharacterOfLine()
-            expect(cursor1.getBufferPosition()).toEqual [0,0]
-            expect(cursor2.getBufferPosition()).toEqual [1,0]
-
-          it "moves to the beginning of the line if it only contains whitespace ", ->
-            editor.setText("first\n    \nthird")
-            editor.setCursorScreenPosition [1,2]
-            editor.moveCursorToFirstCharacterOfLine()
-            cursor = editor.getCursor()
-            expect(cursor.getBufferPosition()).toEqual [1,0]
-
-      describe ".moveCursorToBeginningOfWord()", ->
-        it "moves the cursor to the beginning of the word", ->
-          editor.setCursorBufferPosition [0, 8]
-          editor.addCursorAtBufferPosition [1, 12]
-          editor.addCursorAtBufferPosition [3, 0]
-          [cursor1, cursor2, cursor3] = editor.getCursors()
-
-          editor.moveCursorToBeginningOfWord()
-
-          expect(cursor1.getBufferPosition()).toEqual [0, 4]
-          expect(cursor2.getBufferPosition()).toEqual [1, 11]
-          expect(cursor3.getBufferPosition()).toEqual [2, 39]
-
-        it "does not fail at position [0, 0]", ->
-          editor.setCursorBufferPosition([0, 0])
-          editor.moveCursorToBeginningOfWord()
-
-        it "treats lines with only whitespace as a word", ->
-          editor.setCursorBufferPosition([11, 0])
-          editor.moveCursorToBeginningOfWord()
-          expect(editor.getCursorBufferPosition()).toEqual [10, 0]
-
-        it "works when the current line is blank", ->
-          editor.setCursorBufferPosition([10, 0])
-          editor.moveCursorToBeginningOfWord()
-          expect(editor.getCursorBufferPosition()).toEqual [9, 2]
-
-      describe ".moveCursorToPreviousWordBoundary()", ->
-        it "moves the cursor to the previous word boundary", ->
-          editor.setCursorBufferPosition [0, 8]
-          editor.addCursorAtBufferPosition [2, 0]
-          editor.addCursorAtBufferPosition [2, 4]
-          editor.addCursorAtBufferPosition [3, 14]
-          [cursor1, cursor2, cursor3, cursor4] = editor.getCursors()
-
-          editor.moveCursorToPreviousWordBoundary()
-
-          expect(cursor1.getBufferPosition()).toEqual [0, 4]
-          expect(cursor2.getBufferPosition()).toEqual [1, 30]
-          expect(cursor3.getBufferPosition()).toEqual [2, 0]
-          expect(cursor4.getBufferPosition()).toEqual [3, 13]
-
-      describe ".moveCursorToNextWordBoundary()", ->
-        it "moves the cursor to the previous word boundary", ->
-          editor.setCursorBufferPosition [0, 8]
-          editor.addCursorAtBufferPosition [2, 40]
-          editor.addCursorAtBufferPosition [3, 0]
-          editor.addCursorAtBufferPosition [3, 30]
-          [cursor1, cursor2, cursor3, cursor4] = editor.getCursors()
-
-          editor.moveCursorToNextWordBoundary()
-
-          expect(cursor1.getBufferPosition()).toEqual [0, 13]
-          expect(cursor2.getBufferPosition()).toEqual [3, 0]
-          expect(cursor3.getBufferPosition()).toEqual [3, 4]
-          expect(cursor4.getBufferPosition()).toEqual [3, 31]
-
-      describe ".moveCursorToEndOfWord()", ->
-        it "moves the cursor to the end of the word", ->
-          editor.setCursorBufferPosition [0, 6]
-          editor.addCursorAtBufferPosition [1, 10]
-          editor.addCursorAtBufferPosition [2, 40]
-          [cursor1, cursor2, cursor3] = editor.getCursors()
-
-          editor.moveCursorToEndOfWord()
-
-          expect(cursor1.getBufferPosition()).toEqual [0, 13]
-          expect(cursor2.getBufferPosition()).toEqual [1, 12]
-          expect(cursor3.getBufferPosition()).toEqual [3, 7]
-
-        it "does not blow up when there is no next word", ->
-          editor.setCursorBufferPosition [Infinity, Infinity]
-          endPosition = editor.getCursorBufferPosition()
-          editor.moveCursorToEndOfWord()
-          expect(editor.getCursorBufferPosition()).toEqual endPosition
-
-        it "treats lines with only whitespace as a word", ->
-          editor.setCursorBufferPosition([9, 4])
-          editor.moveCursorToEndOfWord()
-          expect(editor.getCursorBufferPosition()).toEqual [10, 0]
-
-        it "works when the current line is blank", ->
-          editor.setCursorBufferPosition([10, 0])
-          editor.moveCursorToEndOfWord()
-          expect(editor.getCursorBufferPosition()).toEqual [11, 8]
-
-      describe ".moveCursorToBeginningOfNextWord()", ->
-        it "moves the cursor before the first character of the next word", ->
-          editor.setCursorBufferPosition [0,6]
-          editor.addCursorAtBufferPosition [1,11]
-          editor.addCursorAtBufferPosition [2,0]
-          [cursor1, cursor2, cursor3] = editor.getCursors()
-
-          editor.moveCursorToBeginningOfNextWord()
-
-          expect(cursor1.getBufferPosition()).toEqual [0, 14]
-          expect(cursor2.getBufferPosition()).toEqual [1, 13]
-          expect(cursor3.getBufferPosition()).toEqual [2, 4]
-
-          # When the cursor is on whitespace
-          editor.setText("ab cde- ")
-          editor.setCursorBufferPosition [0,2]
-          cursor = editor.getCursor()
-          editor.moveCursorToBeginningOfNextWord()
-
-          expect(cursor.getBufferPosition()).toEqual [0, 3]
-
-        it "does not blow up when there is no next word", ->
-          editor.setCursorBufferPosition [Infinity, Infinity]
-          endPosition = editor.getCursorBufferPosition()
-          editor.moveCursorToBeginningOfNextWord()
-          expect(editor.getCursorBufferPosition()).toEqual endPosition
-
-        it "treats lines with only whitespace as a word", ->
-          editor.setCursorBufferPosition([9, 4])
-          editor.moveCursorToBeginningOfNextWord()
-          expect(editor.getCursorBufferPosition()).toEqual [10, 0]
-
-        it "works when the current line is blank", ->
-          editor.setCursorBufferPosition([10, 0])
-          editor.moveCursorToBeginningOfNextWord()
-          expect(editor.getCursorBufferPosition()).toEqual [11, 9]
-
-      describe ".getCurrentParagraphBufferRange()", ->
-        it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", ->
-          buffer.setText """
-              I am the first paragraph,
-            bordered by the beginning of
-            the file
-            #{'   '}
-
-              I am the second paragraph
-            with blank lines above and below
-            me.
-
-            I am the last paragraph,
-            bordered by the end of the file.
-          """
-
-          # in a paragraph
-          editor.setCursorBufferPosition([1, 7])
-          expect(editor.getCurrentParagraphBufferRange()).toEqual [[0, 0], [2, 8]]
-
-          editor.setCursorBufferPosition([7, 1])
-          expect(editor.getCurrentParagraphBufferRange()).toEqual [[5, 0], [7, 3]]
-
-          editor.setCursorBufferPosition([9, 10])
-          expect(editor.getCurrentParagraphBufferRange()).toEqual [[9, 0], [10, 32]]
-
-          # between paragraphs
-          editor.setCursorBufferPosition([3, 1])
-          expect(editor.getCurrentParagraphBufferRange()).toBeUndefined()
-
-      describe "cursor-moved events", ->
-        cursorMovedHandler = null
-
-        beforeEach ->
-          editor.foldBufferRow(4)
-          editor.setSelectedBufferRange([[8, 1], [9, 0]])
-          cursorMovedHandler = jasmine.createSpy("cursorMovedHandler")
-          editor.on 'cursor-moved', cursorMovedHandler
-
-        describe "when the position of the cursor changes", ->
-          it "emits a cursor-moved event", ->
-            buffer.insert([9, 0], '...')
-            expect(cursorMovedHandler).toHaveBeenCalledWith(
-              oldBufferPosition: [9, 0]
-              oldScreenPosition: [6, 0]
-              newBufferPosition: [9, 3]
-              newScreenPosition: [6, 3]
-              textChanged: true
-            )
-
-        describe "when the position of the associated selection's tail changes, but not the cursor's position", ->
-          it "does not emit a cursor-moved event", ->
-            buffer.insert([8, 0], '...')
-            expect(cursorMovedHandler).not.toHaveBeenCalled()
-
-      describe "addCursorAtScreenPosition(screenPosition)", ->
-        describe "when a cursor already exists at the position", ->
-          it "returns the existing cursor", ->
-            cursor1 = editor.addCursorAtScreenPosition([0,2])
-            cursor2 = editor.addCursorAtScreenPosition([0,2])
-            expect(cursor2.marker).toBe cursor1.marker
-
-      describe "addCursorAtBufferPosition(bufferPosition)", ->
-        describe "when a cursor already exists at the position", ->
-          it "returns the existing cursor", ->
-            cursor1 = editor.addCursorAtBufferPosition([1,4])
-            cursor2 = editor.addCursorAtBufferPosition([1,4])
-            expect(cursor2.marker).toBe cursor1.marker
-
-    describe "selection", ->
-      selection = null
-
-      beforeEach ->
-        selection = editor.getSelection()
-
-      describe ".selectUp/Down/Left/Right()", ->
-        it "expands each selection to its cursor's new location", ->
-          editor.setSelectedBufferRanges([[[0,9], [0,13]], [[3,16], [3,21]]])
-          [selection1, selection2] = editor.getSelections()
-
-          editor.selectRight()
-          expect(selection1.getBufferRange()).toEqual [[0,9], [0,14]]
-          expect(selection2.getBufferRange()).toEqual [[3,16], [3,22]]
-
-          editor.selectLeft()
-          editor.selectLeft()
-          expect(selection1.getBufferRange()).toEqual [[0,9], [0,12]]
-          expect(selection2.getBufferRange()).toEqual [[3,16], [3,20]]
-
-          editor.selectDown()
-          expect(selection1.getBufferRange()).toEqual [[0,9], [1,12]]
-          expect(selection2.getBufferRange()).toEqual [[3,16], [4,20]]
-
-          editor.selectUp()
-          expect(selection1.getBufferRange()).toEqual [[0,9], [0,12]]
-          expect(selection2.getBufferRange()).toEqual [[3,16], [3,20]]
-
-        it "merges selections when they intersect when moving down", ->
-          editor.setSelectedBufferRanges([[[0,9], [0,13]], [[1,10], [1,20]], [[2,15], [3,25]]])
-          [selection1, selection2, selection3] = editor.getSelections()
-
-          editor.selectDown()
-          expect(editor.getSelections()).toEqual [selection1]
-          expect(selection1.getScreenRange()).toEqual([[0, 9], [4, 25]])
-          expect(selection1.isReversed()).toBeFalsy()
-
-        it "merges selections when they intersect when moving up", ->
-          editor.setSelectedBufferRanges([[[0,9], [0,13]], [[1,10], [1,20]]], isReversed: true)
-          [selection1, selection2] = editor.getSelections()
-
-          editor.selectUp()
-
-          expect(editor.getSelections().length).toBe 1
-          expect(editor.getSelections()).toEqual [selection1]
-          expect(selection1.getScreenRange()).toEqual([[0, 0], [1, 20]])
-          expect(selection1.isReversed()).toBeTruthy()
-
-        it "merges selections when they intersect when moving left", ->
-          editor.setSelectedBufferRanges([[[0,9], [0,13]], [[0,14], [1,20]]], isReversed: true)
-          [selection1, selection2] = editor.getSelections()
-
-          editor.selectLeft()
-          expect(editor.getSelections()).toEqual [selection1]
-          expect(selection1.getScreenRange()).toEqual([[0, 8], [1, 20]])
-          expect(selection1.isReversed()).toBeTruthy()
-
-        it "merges selections when they intersect when moving right", ->
-          editor.setSelectedBufferRanges([[[0,9], [0,13]], [[0,14], [1,20]]])
-          [selection1, selection2] = editor.getSelections()
-
-          editor.selectRight()
-          expect(editor.getSelections()).toEqual [selection1]
-          expect(selection1.getScreenRange()).toEqual([[0, 9], [1, 21]])
-          expect(selection1.isReversed()).toBeFalsy()
-
-      describe ".selectToScreenPosition(screenPosition)", ->
-        it "expands the last selection to the given position", ->
-          editor.setSelectedBufferRange([[3, 0], [4, 5]])
-          editor.addCursorAtScreenPosition([5, 6])
-          editor.selectToScreenPosition([6, 2])
-
-          selections = editor.getSelections()
-          expect(selections.length).toBe 2
-          [selection1, selection2] = selections
-          expect(selection1.getScreenRange()).toEqual [[3, 0], [4, 5]]
-          expect(selection2.getScreenRange()).toEqual [[5, 6], [6, 2]]
-
-        it "merges selections if they intersect, maintaining the directionality of the last selection", ->
-          editor.setCursorScreenPosition([4, 10])
-          editor.selectToScreenPosition([5, 27])
-          editor.addCursorAtScreenPosition([3, 10])
-          editor.selectToScreenPosition([6, 27])
-
-          selections = editor.getSelections()
-          expect(selections.length).toBe 1
-          [selection1] = selections
-          expect(selection1.getScreenRange()).toEqual [[3, 10], [6, 27]]
-          expect(selection1.isReversed()).toBeFalsy()
-
-          editor.addCursorAtScreenPosition([7, 4])
-          editor.selectToScreenPosition([4, 11])
-
-          selections = editor.getSelections()
-          expect(selections.length).toBe 1
-          [selection1] = selections
-          expect(selection1.getScreenRange()).toEqual [[3, 10], [7, 4]]
-          expect(selection1.isReversed()).toBeTruthy()
-
-      describe ".selectToTop()", ->
-        it "selects text from cusor position to the top of the buffer", ->
-          editor.setCursorScreenPosition [11,2]
-          editor.addCursorAtScreenPosition [10,0]
-          editor.selectToTop()
-          expect(editor.getCursors().length).toBe 1
-          expect(editor.getCursorBufferPosition()).toEqual [0,0]
-          expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [11,2]]
-          expect(editor.getSelection().isReversed()).toBeTruthy()
-
-      describe ".selectToBottom()", ->
-        it "selects text from cusor position to the bottom of the buffer", ->
-          editor.setCursorScreenPosition [10,0]
-          editor.addCursorAtScreenPosition [9,3]
-          editor.selectToBottom()
-          expect(editor.getCursors().length).toBe 1
-          expect(editor.getCursorBufferPosition()).toEqual [12,2]
-          expect(editor.getSelection().getBufferRange()).toEqual [[9,3], [12,2]]
-          expect(editor.getSelection().isReversed()).toBeFalsy()
-
-      describe ".selectAll()", ->
-        it "selects the entire buffer", ->
-          editor.selectAll()
-          expect(editor.getSelection().getBufferRange()).toEqual buffer.getRange()
-
-      describe ".selectToBeginningOfLine()", ->
-        it "selects text from cusor position to beginning of line", ->
-          editor.setCursorScreenPosition [12,2]
-          editor.addCursorAtScreenPosition [11,3]
-
-          editor.selectToBeginningOfLine()
-
+          expect(cursor.getScreenPosition()).toEqual [1, 9]
+
+      describe "when soft wrap is off", ->
+        it "moves cursor to the end of line", ->
+          editor.setCursorScreenPosition [0,0]
+          editor.addCursorAtScreenPosition [1,0]
+          editor.moveCursorToEndOfScreenLine()
           expect(editor.getCursors().length).toBe 2
           [cursor1, cursor2] = editor.getCursors()
-          expect(cursor1.getBufferPosition()).toEqual [12,0]
-          expect(cursor2.getBufferPosition()).toEqual [11,0]
-
-          expect(editor.getSelections().length).toBe 2
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[12,0], [12,2]]
-          expect(selection1.isReversed()).toBeTruthy()
-          expect(selection2.getBufferRange()).toEqual [[11,0], [11,3]]
-          expect(selection2.isReversed()).toBeTruthy()
-
-      describe ".selectToEndOfLine()", ->
-        it "selects text from cusor position to end of line", ->
-          editor.setCursorScreenPosition [12,0]
-          editor.addCursorAtScreenPosition [11,3]
-
-          editor.selectToEndOfLine()
-
-          expect(editor.getCursors().length).toBe 2
+          expect(cursor1.getBufferPosition()).toEqual [0,29]
+          expect(cursor2.getBufferPosition()).toEqual [1,30]
+
+    describe ".moveCursorToBeginningOfLine()", ->
+      it "moves cursor to the beginning of the buffer line", ->
+        editor.setSoftWrap(true)
+        editor.setEditorWidthInChars(10)
+        editor.setCursorScreenPosition([1, 2])
+        editor.moveCursorToBeginningOfLine()
+        cursor = editor.getCursor()
+        expect(cursor.getScreenPosition()).toEqual [0, 0]
+
+    describe ".moveCursorToEndOfLine()", ->
+      it "moves cursor to the end of the buffer line", ->
+        editor.setSoftWrap(true)
+        editor.setEditorWidthInChars(10)
+        editor.setCursorScreenPosition([0, 2])
+        editor.moveCursorToEndOfLine()
+        cursor = editor.getCursor()
+        expect(cursor.getScreenPosition()).toEqual [3, 4]
+
+    describe ".moveCursorToFirstCharacterOfLine()", ->
+      describe "when soft wrap is on", ->
+        it "moves to the first character of the current screen line or the beginning of the screen line if it's already on the first character", ->
+          editor.setSoftWrap(true)
+          editor.setEditorWidthInChars(10)
+          editor.setCursorScreenPosition [2,5]
+          editor.addCursorAtScreenPosition [8,7]
+
+          editor.moveCursorToFirstCharacterOfLine()
           [cursor1, cursor2] = editor.getCursors()
-          expect(cursor1.getBufferPosition()).toEqual [12,2]
-          expect(cursor2.getBufferPosition()).toEqual [11,44]
-
-          expect(editor.getSelections().length).toBe 2
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[12,0], [12,2]]
-          expect(selection1.isReversed()).toBeFalsy()
-          expect(selection2.getBufferRange()).toEqual [[11,3], [11,44]]
-          expect(selection2.isReversed()).toBeFalsy()
-
-      describe ".selectLine()", ->
-        it "selects the entire line (including newlines) at given row", ->
-          editor.setCursorScreenPosition([1, 2])
-          editor.selectLine()
-          expect(editor.getSelectedBufferRange()).toEqual [[1,0], [2,0]]
-          expect(editor.getSelectedText()).toBe "  var sort = function(items) {\n"
-
-          editor.setCursorScreenPosition([12, 2])
-          editor.selectLine()
-          expect(editor.getSelectedBufferRange()).toEqual [[12,0], [12,2]]
-
-          editor.setCursorBufferPosition([0, 2])
-          editor.selectLine()
-          editor.selectLine()
-          expect(editor.getSelectedBufferRange()).toEqual [[0,0], [2,0]]
-
-      describe ".selectToBeginningOfWord()", ->
-        it "selects text from cusor position to beginning of word", ->
-          editor.setCursorScreenPosition [0,13]
-          editor.addCursorAtScreenPosition [3,49]
-
-          editor.selectToBeginningOfWord()
-
-          expect(editor.getCursors().length).toBe 2
-          [cursor1, cursor2] = editor.getCursors()
-          expect(cursor1.getBufferPosition()).toEqual [0,4]
-          expect(cursor2.getBufferPosition()).toEqual [3,47]
-
-          expect(editor.getSelections().length).toBe 2
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]]
-          expect(selection1.isReversed()).toBeTruthy()
-          expect(selection2.getBufferRange()).toEqual [[3,47], [3,49]]
-          expect(selection2.isReversed()).toBeTruthy()
-
-      describe ".selectToEndOfWord()", ->
-        it "selects text from cusor position to end of word", ->
-          editor.setCursorScreenPosition [0,4]
-          editor.addCursorAtScreenPosition [3,48]
-
-          editor.selectToEndOfWord()
-
-          expect(editor.getCursors().length).toBe 2
-          [cursor1, cursor2] = editor.getCursors()
-          expect(cursor1.getBufferPosition()).toEqual [0,13]
-          expect(cursor2.getBufferPosition()).toEqual [3,50]
-
-          expect(editor.getSelections().length).toBe 2
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]]
-          expect(selection1.isReversed()).toBeFalsy()
-          expect(selection2.getBufferRange()).toEqual [[3,48], [3,50]]
-          expect(selection2.isReversed()).toBeFalsy()
-
-      describe ".selectToBeginningOfNextWord()", ->
-        it "selects text from cusor position to beginning of next word", ->
-          editor.setCursorScreenPosition [0,4]
-          editor.addCursorAtScreenPosition [3,48]
-
-          editor.selectToBeginningOfNextWord()
-
-          expect(editor.getCursors().length).toBe 2
-          [cursor1, cursor2] = editor.getCursors()
-          expect(cursor1.getBufferPosition()).toEqual [0,14]
-          expect(cursor2.getBufferPosition()).toEqual [3,51]
-
-          expect(editor.getSelections().length).toBe 2
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[0,4], [0,14]]
-          expect(selection1.isReversed()).toBeFalsy()
-          expect(selection2.getBufferRange()).toEqual [[3,48], [3,51]]
-          expect(selection2.isReversed()).toBeFalsy()
-
-      describe ".selectToPreviousWordBoundary()", ->
-        it "select to the previous word boundary", ->
-          editor.setCursorBufferPosition [0, 8]
-          editor.addCursorAtBufferPosition [2, 0]
-          editor.addCursorAtBufferPosition [3, 4]
-          editor.addCursorAtBufferPosition [3, 14]
-
-          editor.selectToPreviousWordBoundary()
-
-          expect(editor.getSelections().length).toBe 4
-          [selection1, selection2, selection3, selection4] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[0,8], [0,4]]
-          expect(selection1.isReversed()).toBeTruthy()
-          expect(selection2.getBufferRange()).toEqual [[2,0], [1,30]]
-          expect(selection2.isReversed()).toBeTruthy()
-          expect(selection3.getBufferRange()).toEqual [[3,4], [3,0]]
-          expect(selection3.isReversed()).toBeTruthy()
-          expect(selection4.getBufferRange()).toEqual [[3,14], [3,13]]
-          expect(selection4.isReversed()).toBeTruthy()
-
-      describe ".selectToNextWordBoundary()", ->
-        it "select to the next word boundary", ->
-          editor.setCursorBufferPosition [0, 8]
-          editor.addCursorAtBufferPosition [2, 40]
-          editor.addCursorAtBufferPosition [4, 0]
-          editor.addCursorAtBufferPosition [3, 30]
-
-          editor.selectToNextWordBoundary()
-
-          expect(editor.getSelections().length).toBe 4
-          [selection1, selection2, selection3, selection4] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[0,8], [0,13]]
-          expect(selection1.isReversed()).toBeFalsy()
-          expect(selection2.getBufferRange()).toEqual [[2,40], [3,0]]
-          expect(selection2.isReversed()).toBeFalsy()
-          expect(selection3.getBufferRange()).toEqual [[4,0], [4,4]]
-          expect(selection3.isReversed()).toBeFalsy()
-          expect(selection4.getBufferRange()).toEqual [[3,30], [3,31]]
-          expect(selection4.isReversed()).toBeFalsy()
-
-      describe ".selectWord()", ->
-        describe "when the cursor is inside a word", ->
-          it "selects the entire word", ->
-            editor.setCursorScreenPosition([0, 8])
-            editor.selectWord()
-            expect(editor.getSelectedText()).toBe 'quicksort'
-
-        describe "when the cursor is between two words", ->
-          it "selects the word the cursor is on", ->
-            editor.setCursorScreenPosition([0, 4])
-            editor.selectWord()
-            expect(editor.getSelectedText()).toBe 'quicksort'
-
-            editor.setCursorScreenPosition([0, 3])
-            editor.selectWord()
-            expect(editor.getSelectedText()).toBe 'var'
-
-
-        describe "when the cursor is inside a region of whitespace", ->
-          it "selects the whitespace region", ->
-            editor.setCursorScreenPosition([5, 2])
-            editor.selectWord()
-            expect(editor.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]]
-
-            editor.setCursorScreenPosition([5, 0])
-            editor.selectWord()
-            expect(editor.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]]
-
-        describe "when the cursor is at the end of the text", ->
-          it "select the previous word", ->
-            editor.buffer.append 'word'
-            editor.moveCursorToBottom()
-            editor.selectWord()
-            expect(editor.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]]
-
-      describe ".selectToFirstCharacterOfLine()", ->
+          expect(cursor1.getScreenPosition()).toEqual [2,0]
+          expect(cursor2.getScreenPosition()).toEqual [8,4]
+
+          editor.moveCursorToFirstCharacterOfLine()
+          expect(cursor1.getScreenPosition()).toEqual [2,0]
+          expect(cursor2.getScreenPosition()).toEqual [8,0]
+
+      describe "when soft wrap is off", ->
         it "moves to the first character of the current line or the beginning of the line if it's already on the first character", ->
           editor.setCursorScreenPosition [0,5]
           editor.addCursorAtScreenPosition [1,7]

-          editor.selectToFirstCharacterOfLine()
-
+          editor.moveCursorToFirstCharacterOfLine()
           [cursor1, cursor2] = editor.getCursors()
           expect(cursor1.getBufferPosition()).toEqual [0,0]
           expect(cursor2.getBufferPosition()).toEqual [1,2]

-          expect(editor.getSelections().length).toBe 2
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[0,0], [0,5]]
-          expect(selection1.isReversed()).toBeTruthy()
-          expect(selection2.getBufferRange()).toEqual [[1,2], [1,7]]
-          expect(selection2.isReversed()).toBeTruthy()
-
-          editor.selectToFirstCharacterOfLine()
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1.getBufferRange()).toEqual [[0,0], [0,5]]
-          expect(selection1.isReversed()).toBeTruthy()
-          expect(selection2.getBufferRange()).toEqual [[1,0], [1,7]]
-          expect(selection2.isReversed()).toBeTruthy()
-
-      describe ".setSelectedBufferRanges(ranges)", ->
-        it "clears existing selections and creates selections for each of the given ranges", ->
-          editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]])
-          expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [3, 3]], [[4, 4], [5, 5]]]
-
-          editor.setSelectedBufferRanges([[[5, 5], [6, 6]]])
-          expect(editor.getSelectedBufferRanges()).toEqual [[[5, 5], [6, 6]]]
-
-        it "merges intersecting selections", ->
-          editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]])
-          expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [5, 5]]]
-
-        it "recyles existing selection instances", ->
-          selection = editor.getSelection()
-          editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]])
-
-          [selection1, selection2] = editor.getSelections()
-          expect(selection1).toBe selection
-          expect(selection1.getBufferRange()).toEqual [[2, 2], [3, 3]]
-
-        describe "when the preserveFolds option is false (the default)", ->
-          it "removes folds that contain the selections", ->
-            editor.setSelectedBufferRange([[0,0], [0,0]])
-            editor.createFold(1, 4)
-            editor.createFold(2, 3)
-            editor.createFold(6, 8)
-            editor.createFold(10, 11)
-
-            editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 6], [7, 7]]])
-            expect(editor.lineForScreenRow(1).fold).toBeUndefined()
-            expect(editor.lineForScreenRow(2).fold).toBeUndefined()
-            expect(editor.lineForScreenRow(6).fold).toBeUndefined()
-            expect(editor.lineForScreenRow(10).fold).toBeDefined()
-
-        describe "when the preserve folds option is true", ->
-          it "does not remove folds that contain the selections", ->
-            editor.setSelectedBufferRange([[0,0], [0,0]])
-            editor.createFold(1, 4)
-            editor.createFold(6, 8)
-            editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 0], [6, 1]]], preserveFolds: true)
-            expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
-            expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
-
-      describe ".selectMarker(marker)", ->
-        describe "if the marker is valid", ->
-          it "selects the marker's range and returns the selected range", ->
-            marker = editor.markBufferRange([[0, 1], [3, 3]])
-            expect(editor.selectMarker(marker)).toEqual [[0, 1], [3, 3]]
-            expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [3, 3]]
-
-        describe "if the marker is invalid", ->
-          it "does not change the selection and returns a falsy value", ->
-            marker = editor.markBufferRange([[0, 1], [3, 3]])
-            marker.destroy()
-            expect(editor.selectMarker(marker)).toBeFalsy()
-            expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 0]]
-
-      describe ".addSelectionBelow()", ->
-        describe "when the selection is non-empty", ->
-          it "selects the same region of the line below current selections if possible", ->
-            editor.setSelectedBufferRange([[3, 16], [3, 21]])
-            editor.addSelectionForBufferRange([[3, 25], [3, 34]])
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 16], [3, 21]]
-              [[3, 25], [3, 34]]
-              [[4, 16], [4, 21]]
-              [[4, 25], [4, 29]]
-            ]
-            for cursor in editor.getCursors()
-              expect(cursor.isVisible()).toBeFalsy()
-
-          it "skips lines that are too short to create a non-empty selection", ->
-            editor.setSelectedBufferRange([[3, 31], [3, 38]])
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 31], [3, 38]]
-              [[6, 31], [6, 38]]
-            ]
-
-          it "honors the original selection's range (goal range) when adding across shorter lines", ->
-            editor.setSelectedBufferRange([[3, 22], [3, 38]])
-            editor.addSelectionBelow()
-            editor.addSelectionBelow()
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 22], [3, 38]]
-              [[4, 22], [4, 29]]
-              [[5, 22], [5, 30]]
-              [[6, 22], [6, 38]]
-            ]
-
-          it "clears selection goal ranges when the selection changes", ->
-            editor.setSelectedBufferRange([[3, 22], [3, 38]])
-            editor.addSelectionBelow()
-            editor.selectLeft()
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 22], [3, 37]]
-              [[4, 22], [4, 29]]
-              [[5, 22], [5, 28]]
-            ]
-
-            # goal range from previous add selection is honored next time
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 22], [3, 37]]
-              [[4, 22], [4, 29]]
-              [[5, 22], [5, 30]] # select to end of line 5 because line 4's goal range was reset by line 3 previously
-              [[6, 22], [6, 28]]
-            ]
-
-        describe "when the selection is empty", ->
-          it "does not skip lines that are shorter than the current column", ->
-            editor.setCursorBufferPosition([3, 36])
-            editor.addSelectionBelow()
-            editor.addSelectionBelow()
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 36], [3, 36]]
-              [[4, 29], [4, 29]]
-              [[5, 30], [5, 30]]
-              [[6, 36], [6, 36]]
-            ]
-
-          it "skips empty lines when the column is non-zero", ->
-            editor.setCursorBufferPosition([9, 4])
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[9, 4], [9, 4]]
-              [[11, 4], [11, 4]]
-            ]
-
-          it "does not skip empty lines when the column is zero", ->
-            editor.setCursorBufferPosition([9, 0])
-            editor.addSelectionBelow()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[9, 0], [9, 0]]
-              [[10, 0], [10, 0]]
-            ]
-
-      describe ".addSelectionAbove()", ->
-        describe "when the selection is non-empty", ->
-          it "selects the same region of the line above current selections if possible", ->
-            editor.setSelectedBufferRange([[3, 16], [3, 21]])
-            editor.addSelectionForBufferRange([[3, 37], [3, 44]])
-            editor.addSelectionAbove()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[2, 16], [2, 21]]
-              [[2, 37], [2, 40]]
-              [[3, 16], [3, 21]]
-              [[3, 37], [3, 44]]
-            ]
-            for cursor in editor.getCursors()
-              expect(cursor.isVisible()).toBeFalsy()
-
-          it "skips lines that are too short to create a non-empty selection", ->
-            editor.setSelectedBufferRange([[6, 31], [6, 38]])
-            editor.addSelectionAbove()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 31], [3, 38]]
-              [[6, 31], [6, 38]]
-            ]
-
-          it "honors the original selection's range (goal range) when adding across shorter lines", ->
-            editor.setSelectedBufferRange([[6, 22], [6, 38]])
-            editor.addSelectionAbove()
-            editor.addSelectionAbove()
-            editor.addSelectionAbove()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 22], [3, 38]]
-              [[4, 22], [4, 29]]
-              [[5, 22], [5, 30]]
-              [[6, 22], [6, 38]]
-            ]
-
-        describe "when the selection is empty", ->
-          it "does not skip lines that are shorter than the current column", ->
-            editor.setCursorBufferPosition([6, 36])
-            editor.addSelectionAbove()
-            editor.addSelectionAbove()
-            editor.addSelectionAbove()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[3, 36], [3, 36]]
-              [[4, 29], [4, 29]]
-              [[5, 30], [5, 30]]
-              [[6, 36], [6, 36]]
-            ]
-
-          it "skips empty lines when the column is non-zero", ->
-            editor.setCursorBufferPosition([11, 4])
-            editor.addSelectionAbove()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[9, 4], [9, 4]]
-              [[11, 4], [11, 4]]
-            ]
-
-          it "does not skip empty lines when the column is zero", ->
-            editor.setCursorBufferPosition([10, 0])
-            editor.addSelectionAbove()
-            expect(editor.getSelectedBufferRanges()).toEqual [
-              [[9, 0], [9, 0]]
-              [[10, 0], [10, 0]]
-            ]
-
-      describe ".splitSelectionsIntoLines()", ->
-        it "splits all multi-line selections into one selection per line", ->
-          editor.setSelectedBufferRange([[0, 3], [2, 4]])
-          editor.splitSelectionsIntoLines()
+          editor.moveCursorToFirstCharacterOfLine()
+          expect(cursor1.getBufferPosition()).toEqual [0,0]
+          expect(cursor2.getBufferPosition()).toEqual [1,0]
+
+        it "moves to the beginning of the line if it only contains whitespace ", ->
+          editor.setText("first\n    \nthird")
+          editor.setCursorScreenPosition [1,2]
+          editor.moveCursorToFirstCharacterOfLine()
+          cursor = editor.getCursor()
+          expect(cursor.getBufferPosition()).toEqual [1,0]
+
+    describe ".moveCursorToBeginningOfWord()", ->
+      it "moves the cursor to the beginning of the word", ->
+        editor.setCursorBufferPosition [0, 8]
+        editor.addCursorAtBufferPosition [1, 12]
+        editor.addCursorAtBufferPosition [3, 0]
+        [cursor1, cursor2, cursor3] = editor.getCursors()
+
+        editor.moveCursorToBeginningOfWord()
+
+        expect(cursor1.getBufferPosition()).toEqual [0, 4]
+        expect(cursor2.getBufferPosition()).toEqual [1, 11]
+        expect(cursor3.getBufferPosition()).toEqual [2, 39]
+
+      it "does not fail at position [0, 0]", ->
+        editor.setCursorBufferPosition([0, 0])
+        editor.moveCursorToBeginningOfWord()
+
+      it "treats lines with only whitespace as a word", ->
+        editor.setCursorBufferPosition([11, 0])
+        editor.moveCursorToBeginningOfWord()
+        expect(editor.getCursorBufferPosition()).toEqual [10, 0]
+
+      it "works when the current line is blank", ->
+        editor.setCursorBufferPosition([10, 0])
+        editor.moveCursorToBeginningOfWord()
+        expect(editor.getCursorBufferPosition()).toEqual [9, 2]
+
+    describe ".moveCursorToPreviousWordBoundary()", ->
+      it "moves the cursor to the previous word boundary", ->
+        editor.setCursorBufferPosition [0, 8]
+        editor.addCursorAtBufferPosition [2, 0]
+        editor.addCursorAtBufferPosition [2, 4]
+        editor.addCursorAtBufferPosition [3, 14]
+        [cursor1, cursor2, cursor3, cursor4] = editor.getCursors()
+
+        editor.moveCursorToPreviousWordBoundary()
+
+        expect(cursor1.getBufferPosition()).toEqual [0, 4]
+        expect(cursor2.getBufferPosition()).toEqual [1, 30]
+        expect(cursor3.getBufferPosition()).toEqual [2, 0]
+        expect(cursor4.getBufferPosition()).toEqual [3, 13]
+
+    describe ".moveCursorToNextWordBoundary()", ->
+      it "moves the cursor to the previous word boundary", ->
+        editor.setCursorBufferPosition [0, 8]
+        editor.addCursorAtBufferPosition [2, 40]
+        editor.addCursorAtBufferPosition [3, 0]
+        editor.addCursorAtBufferPosition [3, 30]
+        [cursor1, cursor2, cursor3, cursor4] = editor.getCursors()
+
+        editor.moveCursorToNextWordBoundary()
+
+        expect(cursor1.getBufferPosition()).toEqual [0, 13]
+        expect(cursor2.getBufferPosition()).toEqual [3, 0]
+        expect(cursor3.getBufferPosition()).toEqual [3, 4]
+        expect(cursor4.getBufferPosition()).toEqual [3, 31]
+
+    describe ".moveCursorToEndOfWord()", ->
+      it "moves the cursor to the end of the word", ->
+        editor.setCursorBufferPosition [0, 6]
+        editor.addCursorAtBufferPosition [1, 10]
+        editor.addCursorAtBufferPosition [2, 40]
+        [cursor1, cursor2, cursor3] = editor.getCursors()
+
+        editor.moveCursorToEndOfWord()
+
+        expect(cursor1.getBufferPosition()).toEqual [0, 13]
+        expect(cursor2.getBufferPosition()).toEqual [1, 12]
+        expect(cursor3.getBufferPosition()).toEqual [3, 7]
+
+      it "does not blow up when there is no next word", ->
+        editor.setCursorBufferPosition [Infinity, Infinity]
+        endPosition = editor.getCursorBufferPosition()
+        editor.moveCursorToEndOfWord()
+        expect(editor.getCursorBufferPosition()).toEqual endPosition
+
+      it "treats lines with only whitespace as a word", ->
+        editor.setCursorBufferPosition([9, 4])
+        editor.moveCursorToEndOfWord()
+        expect(editor.getCursorBufferPosition()).toEqual [10, 0]
+
+      it "works when the current line is blank", ->
+        editor.setCursorBufferPosition([10, 0])
+        editor.moveCursorToEndOfWord()
+        expect(editor.getCursorBufferPosition()).toEqual [11, 8]
+
+    describe ".moveCursorToBeginningOfNextWord()", ->
+      it "moves the cursor before the first character of the next word", ->
+        editor.setCursorBufferPosition [0,6]
+        editor.addCursorAtBufferPosition [1,11]
+        editor.addCursorAtBufferPosition [2,0]
+        [cursor1, cursor2, cursor3] = editor.getCursors()
+
+        editor.moveCursorToBeginningOfNextWord()
+
+        expect(cursor1.getBufferPosition()).toEqual [0, 14]
+        expect(cursor2.getBufferPosition()).toEqual [1, 13]
+        expect(cursor3.getBufferPosition()).toEqual [2, 4]
+
+        # When the cursor is on whitespace
+        editor.setText("ab cde- ")
+        editor.setCursorBufferPosition [0,2]
+        cursor = editor.getCursor()
+        editor.moveCursorToBeginningOfNextWord()
+
+        expect(cursor.getBufferPosition()).toEqual [0, 3]
+
+      it "does not blow up when there is no next word", ->
+        editor.setCursorBufferPosition [Infinity, Infinity]
+        endPosition = editor.getCursorBufferPosition()
+        editor.moveCursorToBeginningOfNextWord()
+        expect(editor.getCursorBufferPosition()).toEqual endPosition
+
+      it "treats lines with only whitespace as a word", ->
+        editor.setCursorBufferPosition([9, 4])
+        editor.moveCursorToBeginningOfNextWord()
+        expect(editor.getCursorBufferPosition()).toEqual [10, 0]
+
+      it "works when the current line is blank", ->
+        editor.setCursorBufferPosition([10, 0])
+        editor.moveCursorToBeginningOfNextWord()
+        expect(editor.getCursorBufferPosition()).toEqual [11, 9]
+
+    describe ".getCurrentParagraphBufferRange()", ->
+      it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", ->
+        buffer.setText """
+            I am the first paragraph,
+          bordered by the beginning of
+          the file
+          #{'   '}
+
+            I am the second paragraph
+          with blank lines above and below
+          me.
+
+          I am the last paragraph,
+          bordered by the end of the file.
+        """
+
+        # in a paragraph
+        editor.setCursorBufferPosition([1, 7])
+        expect(editor.getCurrentParagraphBufferRange()).toEqual [[0, 0], [2, 8]]
+
+        editor.setCursorBufferPosition([7, 1])
+        expect(editor.getCurrentParagraphBufferRange()).toEqual [[5, 0], [7, 3]]
+
+        editor.setCursorBufferPosition([9, 10])
+        expect(editor.getCurrentParagraphBufferRange()).toEqual [[9, 0], [10, 32]]
+
+        # between paragraphs
+        editor.setCursorBufferPosition([3, 1])
+        expect(editor.getCurrentParagraphBufferRange()).toBeUndefined()
+
+    describe "cursor-moved events", ->
+      cursorMovedHandler = null
+
+      beforeEach ->
+        editor.foldBufferRow(4)
+        editor.setSelectedBufferRange([[8, 1], [9, 0]])
+        cursorMovedHandler = jasmine.createSpy("cursorMovedHandler")
+        editor.on 'cursor-moved', cursorMovedHandler
+
+      describe "when the position of the cursor changes", ->
+        it "emits a cursor-moved event", ->
+          buffer.insert([9, 0], '...')
+          expect(cursorMovedHandler).toHaveBeenCalledWith(
+            oldBufferPosition: [9, 0]
+            oldScreenPosition: [6, 0]
+            newBufferPosition: [9, 3]
+            newScreenPosition: [6, 3]
+            textChanged: true
+          )
+
+      describe "when the position of the associated selection's tail changes, but not the cursor's position", ->
+        it "does not emit a cursor-moved event", ->
+          buffer.insert([8, 0], '...')
+          expect(cursorMovedHandler).not.toHaveBeenCalled()
+
+    describe "addCursorAtScreenPosition(screenPosition)", ->
+      describe "when a cursor already exists at the position", ->
+        it "returns the existing cursor", ->
+          cursor1 = editor.addCursorAtScreenPosition([0,2])
+          cursor2 = editor.addCursorAtScreenPosition([0,2])
+          expect(cursor2.marker).toBe cursor1.marker
+
+    describe "addCursorAtBufferPosition(bufferPosition)", ->
+      describe "when a cursor already exists at the position", ->
+        it "returns the existing cursor", ->
+          cursor1 = editor.addCursorAtBufferPosition([1,4])
+          cursor2 = editor.addCursorAtBufferPosition([1,4])
+          expect(cursor2.marker).toBe cursor1.marker
+
+  describe "selection", ->
+    selection = null
+
+    beforeEach ->
+      selection = editor.getSelection()
+
+    describe ".selectUp/Down/Left/Right()", ->
+      it "expands each selection to its cursor's new location", ->
+        editor.setSelectedBufferRanges([[[0,9], [0,13]], [[3,16], [3,21]]])
+        [selection1, selection2] = editor.getSelections()
+
+        editor.selectRight()
+        expect(selection1.getBufferRange()).toEqual [[0,9], [0,14]]
+        expect(selection2.getBufferRange()).toEqual [[3,16], [3,22]]
+
+        editor.selectLeft()
+        editor.selectLeft()
+        expect(selection1.getBufferRange()).toEqual [[0,9], [0,12]]
+        expect(selection2.getBufferRange()).toEqual [[3,16], [3,20]]
+
+        editor.selectDown()
+        expect(selection1.getBufferRange()).toEqual [[0,9], [1,12]]
+        expect(selection2.getBufferRange()).toEqual [[3,16], [4,20]]
+
+        editor.selectUp()
+        expect(selection1.getBufferRange()).toEqual [[0,9], [0,12]]
+        expect(selection2.getBufferRange()).toEqual [[3,16], [3,20]]
+
+      it "merges selections when they intersect when moving down", ->
+        editor.setSelectedBufferRanges([[[0,9], [0,13]], [[1,10], [1,20]], [[2,15], [3,25]]])
+        [selection1, selection2, selection3] = editor.getSelections()
+
+        editor.selectDown()
+        expect(editor.getSelections()).toEqual [selection1]
+        expect(selection1.getScreenRange()).toEqual([[0, 9], [4, 25]])
+        expect(selection1.isReversed()).toBeFalsy()
+
+      it "merges selections when they intersect when moving up", ->
+        editor.setSelectedBufferRanges([[[0,9], [0,13]], [[1,10], [1,20]]], isReversed: true)
+        [selection1, selection2] = editor.getSelections()
+
+        editor.selectUp()
+
+        expect(editor.getSelections().length).toBe 1
+        expect(editor.getSelections()).toEqual [selection1]
+        expect(selection1.getScreenRange()).toEqual([[0, 0], [1, 20]])
+        expect(selection1.isReversed()).toBeTruthy()
+
+      it "merges selections when they intersect when moving left", ->
+        editor.setSelectedBufferRanges([[[0,9], [0,13]], [[0,14], [1,20]]], isReversed: true)
+        [selection1, selection2] = editor.getSelections()
+
+        editor.selectLeft()
+        expect(editor.getSelections()).toEqual [selection1]
+        expect(selection1.getScreenRange()).toEqual([[0, 8], [1, 20]])
+        expect(selection1.isReversed()).toBeTruthy()
+
+      it "merges selections when they intersect when moving right", ->
+        editor.setSelectedBufferRanges([[[0,9], [0,13]], [[0,14], [1,20]]])
+        [selection1, selection2] = editor.getSelections()
+
+        editor.selectRight()
+        expect(editor.getSelections()).toEqual [selection1]
+        expect(selection1.getScreenRange()).toEqual([[0, 9], [1, 21]])
+        expect(selection1.isReversed()).toBeFalsy()
+
+    describe ".selectToScreenPosition(screenPosition)", ->
+      it "expands the last selection to the given position", ->
+        editor.setSelectedBufferRange([[3, 0], [4, 5]])
+        editor.addCursorAtScreenPosition([5, 6])
+        editor.selectToScreenPosition([6, 2])
+
+        selections = editor.getSelections()
+        expect(selections.length).toBe 2
+        [selection1, selection2] = selections
+        expect(selection1.getScreenRange()).toEqual [[3, 0], [4, 5]]
+        expect(selection2.getScreenRange()).toEqual [[5, 6], [6, 2]]
+
+      it "merges selections if they intersect, maintaining the directionality of the last selection", ->
+        editor.setCursorScreenPosition([4, 10])
+        editor.selectToScreenPosition([5, 27])
+        editor.addCursorAtScreenPosition([3, 10])
+        editor.selectToScreenPosition([6, 27])
+
+        selections = editor.getSelections()
+        expect(selections.length).toBe 1
+        [selection1] = selections
+        expect(selection1.getScreenRange()).toEqual [[3, 10], [6, 27]]
+        expect(selection1.isReversed()).toBeFalsy()
+
+        editor.addCursorAtScreenPosition([7, 4])
+        editor.selectToScreenPosition([4, 11])
+
+        selections = editor.getSelections()
+        expect(selections.length).toBe 1
+        [selection1] = selections
+        expect(selection1.getScreenRange()).toEqual [[3, 10], [7, 4]]
+        expect(selection1.isReversed()).toBeTruthy()
+
+    describe ".selectToTop()", ->
+      it "selects text from cusor position to the top of the buffer", ->
+        editor.setCursorScreenPosition [11,2]
+        editor.addCursorAtScreenPosition [10,0]
+        editor.selectToTop()
+        expect(editor.getCursors().length).toBe 1
+        expect(editor.getCursorBufferPosition()).toEqual [0,0]
+        expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [11,2]]
+        expect(editor.getSelection().isReversed()).toBeTruthy()
+
+    describe ".selectToBottom()", ->
+      it "selects text from cusor position to the bottom of the buffer", ->
+        editor.setCursorScreenPosition [10,0]
+        editor.addCursorAtScreenPosition [9,3]
+        editor.selectToBottom()
+        expect(editor.getCursors().length).toBe 1
+        expect(editor.getCursorBufferPosition()).toEqual [12,2]
+        expect(editor.getSelection().getBufferRange()).toEqual [[9,3], [12,2]]
+        expect(editor.getSelection().isReversed()).toBeFalsy()
+
+    describe ".selectAll()", ->
+      it "selects the entire buffer", ->
+        editor.selectAll()
+        expect(editor.getSelection().getBufferRange()).toEqual buffer.getRange()
+
+    describe ".selectToBeginningOfLine()", ->
+      it "selects text from cusor position to beginning of line", ->
+        editor.setCursorScreenPosition [12,2]
+        editor.addCursorAtScreenPosition [11,3]
+
+        editor.selectToBeginningOfLine()
+
+        expect(editor.getCursors().length).toBe 2
+        [cursor1, cursor2] = editor.getCursors()
+        expect(cursor1.getBufferPosition()).toEqual [12,0]
+        expect(cursor2.getBufferPosition()).toEqual [11,0]
+
+        expect(editor.getSelections().length).toBe 2
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[12,0], [12,2]]
+        expect(selection1.isReversed()).toBeTruthy()
+        expect(selection2.getBufferRange()).toEqual [[11,0], [11,3]]
+        expect(selection2.isReversed()).toBeTruthy()
+
+    describe ".selectToEndOfLine()", ->
+      it "selects text from cusor position to end of line", ->
+        editor.setCursorScreenPosition [12,0]
+        editor.addCursorAtScreenPosition [11,3]
+
+        editor.selectToEndOfLine()
+
+        expect(editor.getCursors().length).toBe 2
+        [cursor1, cursor2] = editor.getCursors()
+        expect(cursor1.getBufferPosition()).toEqual [12,2]
+        expect(cursor2.getBufferPosition()).toEqual [11,44]
+
+        expect(editor.getSelections().length).toBe 2
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[12,0], [12,2]]
+        expect(selection1.isReversed()).toBeFalsy()
+        expect(selection2.getBufferRange()).toEqual [[11,3], [11,44]]
+        expect(selection2.isReversed()).toBeFalsy()
+
+    describe ".selectLine()", ->
+      it "selects the entire line (including newlines) at given row", ->
+        editor.setCursorScreenPosition([1, 2])
+        editor.selectLine()
+        expect(editor.getSelectedBufferRange()).toEqual [[1,0], [2,0]]
+        expect(editor.getSelectedText()).toBe "  var sort = function(items) {\n"
+
+        editor.setCursorScreenPosition([12, 2])
+        editor.selectLine()
+        expect(editor.getSelectedBufferRange()).toEqual [[12,0], [12,2]]
+
+        editor.setCursorBufferPosition([0, 2])
+        editor.selectLine()
+        editor.selectLine()
+        expect(editor.getSelectedBufferRange()).toEqual [[0,0], [2,0]]
+
+    describe ".selectToBeginningOfWord()", ->
+      it "selects text from cusor position to beginning of word", ->
+        editor.setCursorScreenPosition [0,13]
+        editor.addCursorAtScreenPosition [3,49]
+
+        editor.selectToBeginningOfWord()
+
+        expect(editor.getCursors().length).toBe 2
+        [cursor1, cursor2] = editor.getCursors()
+        expect(cursor1.getBufferPosition()).toEqual [0,4]
+        expect(cursor2.getBufferPosition()).toEqual [3,47]
+
+        expect(editor.getSelections().length).toBe 2
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]]
+        expect(selection1.isReversed()).toBeTruthy()
+        expect(selection2.getBufferRange()).toEqual [[3,47], [3,49]]
+        expect(selection2.isReversed()).toBeTruthy()
+
+    describe ".selectToEndOfWord()", ->
+      it "selects text from cusor position to end of word", ->
+        editor.setCursorScreenPosition [0,4]
+        editor.addCursorAtScreenPosition [3,48]
+
+        editor.selectToEndOfWord()
+
+        expect(editor.getCursors().length).toBe 2
+        [cursor1, cursor2] = editor.getCursors()
+        expect(cursor1.getBufferPosition()).toEqual [0,13]
+        expect(cursor2.getBufferPosition()).toEqual [3,50]
+
+        expect(editor.getSelections().length).toBe 2
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]]
+        expect(selection1.isReversed()).toBeFalsy()
+        expect(selection2.getBufferRange()).toEqual [[3,48], [3,50]]
+        expect(selection2.isReversed()).toBeFalsy()
+
+    describe ".selectToBeginningOfNextWord()", ->
+      it "selects text from cusor position to beginning of next word", ->
+        editor.setCursorScreenPosition [0,4]
+        editor.addCursorAtScreenPosition [3,48]
+
+        editor.selectToBeginningOfNextWord()
+
+        expect(editor.getCursors().length).toBe 2
+        [cursor1, cursor2] = editor.getCursors()
+        expect(cursor1.getBufferPosition()).toEqual [0,14]
+        expect(cursor2.getBufferPosition()).toEqual [3,51]
+
+        expect(editor.getSelections().length).toBe 2
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[0,4], [0,14]]
+        expect(selection1.isReversed()).toBeFalsy()
+        expect(selection2.getBufferRange()).toEqual [[3,48], [3,51]]
+        expect(selection2.isReversed()).toBeFalsy()
+
+    describe ".selectToPreviousWordBoundary()", ->
+      it "select to the previous word boundary", ->
+        editor.setCursorBufferPosition [0, 8]
+        editor.addCursorAtBufferPosition [2, 0]
+        editor.addCursorAtBufferPosition [3, 4]
+        editor.addCursorAtBufferPosition [3, 14]
+
+        editor.selectToPreviousWordBoundary()
+
+        expect(editor.getSelections().length).toBe 4
+        [selection1, selection2, selection3, selection4] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[0,8], [0,4]]
+        expect(selection1.isReversed()).toBeTruthy()
+        expect(selection2.getBufferRange()).toEqual [[2,0], [1,30]]
+        expect(selection2.isReversed()).toBeTruthy()
+        expect(selection3.getBufferRange()).toEqual [[3,4], [3,0]]
+        expect(selection3.isReversed()).toBeTruthy()
+        expect(selection4.getBufferRange()).toEqual [[3,14], [3,13]]
+        expect(selection4.isReversed()).toBeTruthy()
+
+    describe ".selectToNextWordBoundary()", ->
+      it "select to the next word boundary", ->
+        editor.setCursorBufferPosition [0, 8]
+        editor.addCursorAtBufferPosition [2, 40]
+        editor.addCursorAtBufferPosition [4, 0]
+        editor.addCursorAtBufferPosition [3, 30]
+
+        editor.selectToNextWordBoundary()
+
+        expect(editor.getSelections().length).toBe 4
+        [selection1, selection2, selection3, selection4] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[0,8], [0,13]]
+        expect(selection1.isReversed()).toBeFalsy()
+        expect(selection2.getBufferRange()).toEqual [[2,40], [3,0]]
+        expect(selection2.isReversed()).toBeFalsy()
+        expect(selection3.getBufferRange()).toEqual [[4,0], [4,4]]
+        expect(selection3.isReversed()).toBeFalsy()
+        expect(selection4.getBufferRange()).toEqual [[3,30], [3,31]]
+        expect(selection4.isReversed()).toBeFalsy()
+
+    describe ".selectWord()", ->
+      describe "when the cursor is inside a word", ->
+        it "selects the entire word", ->
+          editor.setCursorScreenPosition([0, 8])
+          editor.selectWord()
+          expect(editor.getSelectedText()).toBe 'quicksort'
+
+      describe "when the cursor is between two words", ->
+        it "selects the word the cursor is on", ->
+          editor.setCursorScreenPosition([0, 4])
+          editor.selectWord()
+          expect(editor.getSelectedText()).toBe 'quicksort'
+
+          editor.setCursorScreenPosition([0, 3])
+          editor.selectWord()
+          expect(editor.getSelectedText()).toBe 'var'
+
+
+      describe "when the cursor is inside a region of whitespace", ->
+        it "selects the whitespace region", ->
+          editor.setCursorScreenPosition([5, 2])
+          editor.selectWord()
+          expect(editor.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]]
+
+          editor.setCursorScreenPosition([5, 0])
+          editor.selectWord()
+          expect(editor.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]]
+
+      describe "when the cursor is at the end of the text", ->
+        it "select the previous word", ->
+          editor.buffer.append 'word'
+          editor.moveCursorToBottom()
+          editor.selectWord()
+          expect(editor.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]]
+
+    describe ".selectToFirstCharacterOfLine()", ->
+      it "moves to the first character of the current line or the beginning of the line if it's already on the first character", ->
+        editor.setCursorScreenPosition [0,5]
+        editor.addCursorAtScreenPosition [1,7]
+
+        editor.selectToFirstCharacterOfLine()
+
+        [cursor1, cursor2] = editor.getCursors()
+        expect(cursor1.getBufferPosition()).toEqual [0,0]
+        expect(cursor2.getBufferPosition()).toEqual [1,2]
+
+        expect(editor.getSelections().length).toBe 2
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[0,0], [0,5]]
+        expect(selection1.isReversed()).toBeTruthy()
+        expect(selection2.getBufferRange()).toEqual [[1,2], [1,7]]
+        expect(selection2.isReversed()).toBeTruthy()
+
+        editor.selectToFirstCharacterOfLine()
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1.getBufferRange()).toEqual [[0,0], [0,5]]
+        expect(selection1.isReversed()).toBeTruthy()
+        expect(selection2.getBufferRange()).toEqual [[1,0], [1,7]]
+        expect(selection2.isReversed()).toBeTruthy()
+
+    describe ".setSelectedBufferRanges(ranges)", ->
+      it "clears existing selections and creates selections for each of the given ranges", ->
+        editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]])
+        expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [3, 3]], [[4, 4], [5, 5]]]
+
+        editor.setSelectedBufferRanges([[[5, 5], [6, 6]]])
+        expect(editor.getSelectedBufferRanges()).toEqual [[[5, 5], [6, 6]]]
+
+      it "merges intersecting selections", ->
+        editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[3, 0], [5, 5]]])
+        expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [5, 5]]]
+
+      it "recyles existing selection instances", ->
+        selection = editor.getSelection()
+        editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[4, 4], [5, 5]]])
+
+        [selection1, selection2] = editor.getSelections()
+        expect(selection1).toBe selection
+        expect(selection1.getBufferRange()).toEqual [[2, 2], [3, 3]]
+
+      describe "when the preserveFolds option is false (the default)", ->
+        it "removes folds that contain the selections", ->
+          editor.setSelectedBufferRange([[0,0], [0,0]])
+          editor.createFold(1, 4)
+          editor.createFold(2, 3)
+          editor.createFold(6, 8)
+          editor.createFold(10, 11)
+
+          editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 6], [7, 7]]])
+          expect(editor.lineForScreenRow(1).fold).toBeUndefined()
+          expect(editor.lineForScreenRow(2).fold).toBeUndefined()
+          expect(editor.lineForScreenRow(6).fold).toBeUndefined()
+          expect(editor.lineForScreenRow(10).fold).toBeDefined()
+
+      describe "when the preserve folds option is true", ->
+        it "does not remove folds that contain the selections", ->
+          editor.setSelectedBufferRange([[0,0], [0,0]])
+          editor.createFold(1, 4)
+          editor.createFold(6, 8)
+          editor.setSelectedBufferRanges([[[2, 2], [3, 3]], [[6, 0], [6, 1]]], preserveFolds: true)
+          expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
+          expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+
+    describe ".selectMarker(marker)", ->
+      describe "if the marker is valid", ->
+        it "selects the marker's range and returns the selected range", ->
+          marker = editor.markBufferRange([[0, 1], [3, 3]])
+          expect(editor.selectMarker(marker)).toEqual [[0, 1], [3, 3]]
+          expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [3, 3]]
+
+      describe "if the marker is invalid", ->
+        it "does not change the selection and returns a falsy value", ->
+          marker = editor.markBufferRange([[0, 1], [3, 3]])
+          marker.destroy()
+          expect(editor.selectMarker(marker)).toBeFalsy()
+          expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 0]]
+
+    describe ".addSelectionBelow()", ->
+      describe "when the selection is non-empty", ->
+        it "selects the same region of the line below current selections if possible", ->
+          editor.setSelectedBufferRange([[3, 16], [3, 21]])
+          editor.addSelectionForBufferRange([[3, 25], [3, 34]])
+          editor.addSelectionBelow()
           expect(editor.getSelectedBufferRanges()).toEqual [
-            [[0, 3], [0, 29]]
-            [[1, 0], [1, 30]]
-            [[2, 0], [2, 4]]
+            [[3, 16], [3, 21]]
+            [[3, 25], [3, 34]]
+            [[4, 16], [4, 21]]
+            [[4, 25], [4, 29]]
           ]
-
-          editor.setSelectedBufferRange([[0, 3], [1, 10]])
-          editor.splitSelectionsIntoLines()
+          for cursor in editor.getCursors()
+            expect(cursor.isVisible()).toBeFalsy()
+
+        it "skips lines that are too short to create a non-empty selection", ->
+          editor.setSelectedBufferRange([[3, 31], [3, 38]])
+          editor.addSelectionBelow()
           expect(editor.getSelectedBufferRanges()).toEqual [
-            [[0, 3], [0, 29]]
-            [[1, 0], [1, 10]]
+            [[3, 31], [3, 38]]
+            [[6, 31], [6, 38]]
           ]

-          editor.setSelectedBufferRange([[0, 0], [0, 3]])
-          editor.splitSelectionsIntoLines()
-          expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
-
-      describe ".consolidateSelections()", ->
-        it "destroys all selections but the most recent, returning true if any selections were destroyed", ->
+        it "honors the original selection's range (goal range) when adding across shorter lines", ->
+          editor.setSelectedBufferRange([[3, 22], [3, 38]])
+          editor.addSelectionBelow()
+          editor.addSelectionBelow()
+          editor.addSelectionBelow()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 22], [3, 38]]
+            [[4, 22], [4, 29]]
+            [[5, 22], [5, 30]]
+            [[6, 22], [6, 38]]
+          ]
+
+        it "clears selection goal ranges when the selection changes", ->
+          editor.setSelectedBufferRange([[3, 22], [3, 38]])
+          editor.addSelectionBelow()
+          editor.selectLeft()
+          editor.addSelectionBelow()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 22], [3, 37]]
+            [[4, 22], [4, 29]]
+            [[5, 22], [5, 28]]
+          ]
+
+          # goal range from previous add selection is honored next time
+          editor.addSelectionBelow()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 22], [3, 37]]
+            [[4, 22], [4, 29]]
+            [[5, 22], [5, 30]] # select to end of line 5 because line 4's goal range was reset by line 3 previously
+            [[6, 22], [6, 28]]
+          ]
+
+      describe "when the selection is empty", ->
+        it "does not skip lines that are shorter than the current column", ->
+          editor.setCursorBufferPosition([3, 36])
+          editor.addSelectionBelow()
+          editor.addSelectionBelow()
+          editor.addSelectionBelow()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 36], [3, 36]]
+            [[4, 29], [4, 29]]
+            [[5, 30], [5, 30]]
+            [[6, 36], [6, 36]]
+          ]
+
+        it "skips empty lines when the column is non-zero", ->
+          editor.setCursorBufferPosition([9, 4])
+          editor.addSelectionBelow()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[9, 4], [9, 4]]
+            [[11, 4], [11, 4]]
+          ]
+
+        it "does not skip empty lines when the column is zero", ->
+          editor.setCursorBufferPosition([9, 0])
+          editor.addSelectionBelow()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[9, 0], [9, 0]]
+            [[10, 0], [10, 0]]
+          ]
+
+    describe ".addSelectionAbove()", ->
+      describe "when the selection is non-empty", ->
+        it "selects the same region of the line above current selections if possible", ->
           editor.setSelectedBufferRange([[3, 16], [3, 21]])
-          selection1 = editor.getSelection()
-          selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
-          selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]])
-
-          expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
-          expect(editor.consolidateSelections()).toBeTruthy()
-          expect(editor.getSelections()).toEqual [selection3]
-          expect(selection3.isEmpty()).toBeFalsy()
-          expect(editor.consolidateSelections()).toBeFalsy()
-          expect(editor.getSelections()).toEqual [selection3]
-
-      describe "when the cursor is moved while there is a selection", ->
-        makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
-
-        it "clears the selection", ->
-          makeSelection()
-          editor.moveCursorDown()
-          expect(selection.isEmpty()).toBeTruthy()
-
-          makeSelection()
-          editor.moveCursorUp()
-          expect(selection.isEmpty()).toBeTruthy()
-
-          makeSelection()
-          editor.moveCursorLeft()
-          expect(selection.isEmpty()).toBeTruthy()
-
-          makeSelection()
-          editor.moveCursorRight()
-          expect(selection.isEmpty()).toBeTruthy()
-
-          makeSelection()
-          editor.setCursorScreenPosition([3, 3])
-          expect(selection.isEmpty()).toBeTruthy()
-
-      it "does not share selections between different edit sessions for the same buffer", ->
-        editor2 = atom.project.openSync('sample.js')
-        editor.setSelectedBufferRanges([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
-        editor2.setSelectedBufferRanges([[[8, 7], [6, 5]], [[4, 3], [2, 1]]])
-        expect(editor2.getSelectedBufferRanges()).not.toEqual editor.getSelectedBufferRanges()
-
-    describe "buffer manipulation", ->
-      describe ".insertText(text)", ->
-        describe "when there are multiple empty selections", ->
-          describe "when the cursors are on the same line", ->
-            it "inserts the given text at the location of each cursor and moves the cursors to the end of each cursor's inserted text", ->
-              editor.setCursorScreenPosition([1, 2])
-              editor.addCursorAtScreenPosition([1, 5])
-
-              editor.insertText('xxx')
-
-              expect(buffer.lineForRow(1)).toBe '  xxxvarxxx sort = function(items) {'
-              [cursor1, cursor2] = editor.getCursors()
-
-              expect(cursor1.getBufferPosition()).toEqual [1, 5]
-              expect(cursor2.getBufferPosition()).toEqual [1, 11]
-
-          describe "when the cursors are on different lines", ->
-            it "inserts the given text at the location of each cursor and moves the cursors to the end of each cursor's inserted text", ->
-              editor.setCursorScreenPosition([1, 2])
-              editor.addCursorAtScreenPosition([2, 4])
-
-              editor.insertText('xxx')
-
-              expect(buffer.lineForRow(1)).toBe '  xxxvar sort = function(items) {'
-              expect(buffer.lineForRow(2)).toBe '    xxxif (items.length <= 1) return items;'
-              [cursor1, cursor2] = editor.getCursors()
-
-              expect(cursor1.getBufferPosition()).toEqual [1, 5]
-              expect(cursor2.getBufferPosition()).toEqual [2, 7]
-
-        describe "when there are multiple non-empty selections", ->
-          describe "when the selections are on the same line", ->
-            it "replaces each selection range with the inserted characters", ->
-              editor.setSelectedBufferRanges([[[0,4], [0,13]], [[0,22], [0,24]]])
-              editor.insertText("x")
-
-              [cursor1, cursor2] = editor.getCursors()
-              [selection1, selection2] = editor.getSelections()
-
-              expect(cursor1.getScreenPosition()).toEqual [0, 5]
-              expect(cursor2.getScreenPosition()).toEqual [0, 15]
-              expect(selection1.isEmpty()).toBeTruthy()
-              expect(selection2.isEmpty()).toBeTruthy()
-
-              expect(editor.lineForBufferRow(0)).toBe "var x = functix () {"
-
-          describe "when the selections are on different lines", ->
-            it "replaces each selection with the given text, clears the selections, and places the cursor at the end of each selection's inserted text", ->
-              editor.setSelectedBufferRanges([[[1, 0], [1, 2]], [[2, 0], [2, 4]]])
-
-              editor.insertText('xxx')
-
-              expect(buffer.lineForRow(1)).toBe 'xxxvar sort = function(items) {'
-              expect(buffer.lineForRow(2)).toBe 'xxxif (items.length <= 1) return items;'
-              [selection1, selection2] = editor.getSelections()
-
-              expect(selection1.isEmpty()).toBeTruthy()
-              expect(selection1.cursor.getBufferPosition()).toEqual [1, 3]
-              expect(selection2.isEmpty()).toBeTruthy()
-              expect(selection2.cursor.getBufferPosition()).toEqual [2, 3]
-
-        describe "when there is a selection that ends on a folded line", ->
-          it "destroys the selection", ->
-            editor.createFold(2,4)
-            editor.setSelectedBufferRange([[1,0], [2,0]])
-            editor.insertText('holy cow')
-            expect(editor.lineForScreenRow(2).fold).toBeUndefined()
-
-      describe ".insertNewline()", ->
-        describe "when there is a single cursor", ->
-          describe "when the cursor is at the beginning of a line", ->
-            it "inserts an empty line before it", ->
-              editor.setCursorScreenPosition(row: 1, column: 0)
-
-              editor.insertNewline()
-
-              expect(buffer.lineForRow(1)).toBe ''
-              expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
-
-          describe "when the cursor is in the middle of a line", ->
-            it "splits the current line to form a new line", ->
-              editor.setCursorScreenPosition(row: 1, column: 6)
-              originalLine = buffer.lineForRow(1)
-              lineBelowOriginalLine = buffer.lineForRow(2)
-
-              editor.insertNewline()
-
-              expect(buffer.lineForRow(1)).toBe originalLine[0...6]
-              expect(buffer.lineForRow(2)).toBe originalLine[6..]
-              expect(buffer.lineForRow(3)).toBe lineBelowOriginalLine
-              expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
-
-          describe "when the cursor is on the end of a line", ->
-            it "inserts an empty line after it", ->
-              editor.setCursorScreenPosition(row: 1, column: buffer.lineForRow(1).length)
-
-              editor.insertNewline()
-
-              expect(buffer.lineForRow(2)).toBe ''
-              expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
-
-        describe "when there are multiple cursors", ->
-          describe "when the cursors are on the same line", ->
-            it "breaks the line at the cursor locations", ->
-              editor.setCursorScreenPosition([3, 13])
-              editor.addCursorAtScreenPosition([3, 38])
-
-              editor.insertNewline()
-
-              expect(editor.lineForBufferRow(3)).toBe "    var pivot"
-              expect(editor.lineForBufferRow(4)).toBe " = items.shift(), current"
-              expect(editor.lineForBufferRow(5)).toBe ", left = [], right = [];"
-              expect(editor.lineForBufferRow(6)).toBe "    while(items.length > 0) {"
-
-              [cursor1, cursor2] = editor.getCursors()
-              expect(cursor1.getBufferPosition()).toEqual [4, 0]
-              expect(cursor2.getBufferPosition()).toEqual [5, 0]
-
-          describe "when the cursors are on different lines", ->
-            it "inserts newlines at each cursor location", ->
-              editor.setCursorScreenPosition([3, 0])
-              editor.addCursorAtScreenPosition([6, 0])
-
-              editor.insertText("\n")
-              expect(editor.lineForBufferRow(3)).toBe ""
-              expect(editor.lineForBufferRow(4)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
-              expect(editor.lineForBufferRow(5)).toBe "    while(items.length > 0) {"
-              expect(editor.lineForBufferRow(6)).toBe "      current = items.shift();"
-              expect(editor.lineForBufferRow(7)).toBe ""
-              expect(editor.lineForBufferRow(8)).toBe "      current < pivot ? left.push(current) : right.push(current);"
-              expect(editor.lineForBufferRow(9)).toBe "    }"
-
-              [cursor1, cursor2] = editor.getCursors()
-              expect(cursor1.getBufferPosition()).toEqual [4,0]
-              expect(cursor2.getBufferPosition()).toEqual [8,0]
-
-      describe ".insertNewlineBelow()", ->
-        describe "when the operation is undone", ->
-          it "places the cursor back at the previous location", ->
-            editor.setCursorBufferPosition([0,2])
-            editor.insertNewlineBelow()
-            expect(editor.getCursorBufferPosition()).toEqual [1,0]
-            editor.undo()
-            expect(editor.getCursorBufferPosition()).toEqual [0,2]
-
-        it "inserts a newline below the cursor's current line, autoindents it, and moves the cursor to the end of the line", ->
-          atom.config.set("editor.autoIndent", true)
+          editor.addSelectionForBufferRange([[3, 37], [3, 44]])
+          editor.addSelectionAbove()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[2, 16], [2, 21]]
+            [[2, 37], [2, 40]]
+            [[3, 16], [3, 21]]
+            [[3, 37], [3, 44]]
+          ]
+          for cursor in editor.getCursors()
+            expect(cursor.isVisible()).toBeFalsy()
+
+        it "skips lines that are too short to create a non-empty selection", ->
+          editor.setSelectedBufferRange([[6, 31], [6, 38]])
+          editor.addSelectionAbove()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 31], [3, 38]]
+            [[6, 31], [6, 38]]
+          ]
+
+        it "honors the original selection's range (goal range) when adding across shorter lines", ->
+          editor.setSelectedBufferRange([[6, 22], [6, 38]])
+          editor.addSelectionAbove()
+          editor.addSelectionAbove()
+          editor.addSelectionAbove()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 22], [3, 38]]
+            [[4, 22], [4, 29]]
+            [[5, 22], [5, 30]]
+            [[6, 22], [6, 38]]
+          ]
+
+      describe "when the selection is empty", ->
+        it "does not skip lines that are shorter than the current column", ->
+          editor.setCursorBufferPosition([6, 36])
+          editor.addSelectionAbove()
+          editor.addSelectionAbove()
+          editor.addSelectionAbove()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 36], [3, 36]]
+            [[4, 29], [4, 29]]
+            [[5, 30], [5, 30]]
+            [[6, 36], [6, 36]]
+          ]
+
+        it "skips empty lines when the column is non-zero", ->
+          editor.setCursorBufferPosition([11, 4])
+          editor.addSelectionAbove()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[9, 4], [9, 4]]
+            [[11, 4], [11, 4]]
+          ]
+
+        it "does not skip empty lines when the column is zero", ->
+          editor.setCursorBufferPosition([10, 0])
+          editor.addSelectionAbove()
+          expect(editor.getSelectedBufferRanges()).toEqual [
+            [[9, 0], [9, 0]]
+            [[10, 0], [10, 0]]
+          ]
+
+    describe ".splitSelectionsIntoLines()", ->
+      it "splits all multi-line selections into one selection per line", ->
+        editor.setSelectedBufferRange([[0, 3], [2, 4]])
+        editor.splitSelectionsIntoLines()
+        expect(editor.getSelectedBufferRanges()).toEqual [
+          [[0, 3], [0, 29]]
+          [[1, 0], [1, 30]]
+          [[2, 0], [2, 4]]
+        ]
+
+        editor.setSelectedBufferRange([[0, 3], [1, 10]])
+        editor.splitSelectionsIntoLines()
+        expect(editor.getSelectedBufferRanges()).toEqual [
+          [[0, 3], [0, 29]]
+          [[1, 0], [1, 10]]
+        ]
+
+        editor.setSelectedBufferRange([[0, 0], [0, 3]])
+        editor.splitSelectionsIntoLines()
+        expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
+
+    describe ".consolidateSelections()", ->
+      it "destroys all selections but the most recent, returning true if any selections were destroyed", ->
+        editor.setSelectedBufferRange([[3, 16], [3, 21]])
+        selection1 = editor.getSelection()
+        selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
+        selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]])
+
+        expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
+        expect(editor.consolidateSelections()).toBeTruthy()
+        expect(editor.getSelections()).toEqual [selection3]
+        expect(selection3.isEmpty()).toBeFalsy()
+        expect(editor.consolidateSelections()).toBeFalsy()
+        expect(editor.getSelections()).toEqual [selection3]
+
+    describe "when the cursor is moved while there is a selection", ->
+      makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
+
+      it "clears the selection", ->
+        makeSelection()
+        editor.moveCursorDown()
+        expect(selection.isEmpty()).toBeTruthy()
+
+        makeSelection()
+        editor.moveCursorUp()
+        expect(selection.isEmpty()).toBeTruthy()
+
+        makeSelection()
+        editor.moveCursorLeft()
+        expect(selection.isEmpty()).toBeTruthy()
+
+        makeSelection()
+        editor.moveCursorRight()
+        expect(selection.isEmpty()).toBeTruthy()
+
+        makeSelection()
+        editor.setCursorScreenPosition([3, 3])
+        expect(selection.isEmpty()).toBeTruthy()
+
+    it "does not share selections between different edit sessions for the same buffer", ->
+      editor2 = atom.project.openSync('sample.js')
+      editor.setSelectedBufferRanges([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
+      editor2.setSelectedBufferRanges([[[8, 7], [6, 5]], [[4, 3], [2, 1]]])
+      expect(editor2.getSelectedBufferRanges()).not.toEqual editor.getSelectedBufferRanges()
+
+  describe "buffer manipulation", ->
+    describe ".insertText(text)", ->
+      describe "when there are multiple empty selections", ->
+        describe "when the cursors are on the same line", ->
+          it "inserts the given text at the location of each cursor and moves the cursors to the end of each cursor's inserted text", ->
+            editor.setCursorScreenPosition([1, 2])
+            editor.addCursorAtScreenPosition([1, 5])
+
+            editor.insertText('xxx')
+
+            expect(buffer.lineForRow(1)).toBe '  xxxvarxxx sort = function(items) {'
+            [cursor1, cursor2] = editor.getCursors()
+
+            expect(cursor1.getBufferPosition()).toEqual [1, 5]
+            expect(cursor2.getBufferPosition()).toEqual [1, 11]
+
+        describe "when the cursors are on different lines", ->
+          it "inserts the given text at the location of each cursor and moves the cursors to the end of each cursor's inserted text", ->
+            editor.setCursorScreenPosition([1, 2])
+            editor.addCursorAtScreenPosition([2, 4])
+
+            editor.insertText('xxx')
+
+            expect(buffer.lineForRow(1)).toBe '  xxxvar sort = function(items) {'
+            expect(buffer.lineForRow(2)).toBe '    xxxif (items.length <= 1) return items;'
+            [cursor1, cursor2] = editor.getCursors()
+
+            expect(cursor1.getBufferPosition()).toEqual [1, 5]
+            expect(cursor2.getBufferPosition()).toEqual [2, 7]
+
+      describe "when there are multiple non-empty selections", ->
+        describe "when the selections are on the same line", ->
+          it "replaces each selection range with the inserted characters", ->
+            editor.setSelectedBufferRanges([[[0,4], [0,13]], [[0,22], [0,24]]])
+            editor.insertText("x")
+
+            [cursor1, cursor2] = editor.getCursors()
+            [selection1, selection2] = editor.getSelections()
+
+            expect(cursor1.getScreenPosition()).toEqual [0, 5]
+            expect(cursor2.getScreenPosition()).toEqual [0, 15]
+            expect(selection1.isEmpty()).toBeTruthy()
+            expect(selection2.isEmpty()).toBeTruthy()
+
+            expect(editor.lineForBufferRow(0)).toBe "var x = functix () {"
+
+        describe "when the selections are on different lines", ->
+          it "replaces each selection with the given text, clears the selections, and places the cursor at the end of each selection's inserted text", ->
+            editor.setSelectedBufferRanges([[[1, 0], [1, 2]], [[2, 0], [2, 4]]])
+
+            editor.insertText('xxx')
+
+            expect(buffer.lineForRow(1)).toBe 'xxxvar sort = function(items) {'
+            expect(buffer.lineForRow(2)).toBe 'xxxif (items.length <= 1) return items;'
+            [selection1, selection2] = editor.getSelections()
+
+            expect(selection1.isEmpty()).toBeTruthy()
+            expect(selection1.cursor.getBufferPosition()).toEqual [1, 3]
+            expect(selection2.isEmpty()).toBeTruthy()
+            expect(selection2.cursor.getBufferPosition()).toEqual [2, 3]
+
+      describe "when there is a selection that ends on a folded line", ->
+        it "destroys the selection", ->
+          editor.createFold(2,4)
+          editor.setSelectedBufferRange([[1,0], [2,0]])
+          editor.insertText('holy cow')
+          expect(editor.lineForScreenRow(2).fold).toBeUndefined()
+
+    describe ".insertNewline()", ->
+      describe "when there is a single cursor", ->
+        describe "when the cursor is at the beginning of a line", ->
+          it "inserts an empty line before it", ->
+            editor.setCursorScreenPosition(row: 1, column: 0)
+
+            editor.insertNewline()
+
+            expect(buffer.lineForRow(1)).toBe ''
+            expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
+
+        describe "when the cursor is in the middle of a line", ->
+          it "splits the current line to form a new line", ->
+            editor.setCursorScreenPosition(row: 1, column: 6)
+            originalLine = buffer.lineForRow(1)
+            lineBelowOriginalLine = buffer.lineForRow(2)
+
+            editor.insertNewline()
+
+            expect(buffer.lineForRow(1)).toBe originalLine[0...6]
+            expect(buffer.lineForRow(2)).toBe originalLine[6..]
+            expect(buffer.lineForRow(3)).toBe lineBelowOriginalLine
+            expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
+
+        describe "when the cursor is on the end of a line", ->
+          it "inserts an empty line after it", ->
+            editor.setCursorScreenPosition(row: 1, column: buffer.lineForRow(1).length)
+
+            editor.insertNewline()
+
+            expect(buffer.lineForRow(2)).toBe ''
+            expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
+
+      describe "when there are multiple cursors", ->
+        describe "when the cursors are on the same line", ->
+          it "breaks the line at the cursor locations", ->
+            editor.setCursorScreenPosition([3, 13])
+            editor.addCursorAtScreenPosition([3, 38])
+
+            editor.insertNewline()
+
+            expect(editor.lineForBufferRow(3)).toBe "    var pivot"
+            expect(editor.lineForBufferRow(4)).toBe " = items.shift(), current"
+            expect(editor.lineForBufferRow(5)).toBe ", left = [], right = [];"
+            expect(editor.lineForBufferRow(6)).toBe "    while(items.length > 0) {"
+
+            [cursor1, cursor2] = editor.getCursors()
+            expect(cursor1.getBufferPosition()).toEqual [4, 0]
+            expect(cursor2.getBufferPosition()).toEqual [5, 0]
+
+        describe "when the cursors are on different lines", ->
+          it "inserts newlines at each cursor location", ->
+            editor.setCursorScreenPosition([3, 0])
+            editor.addCursorAtScreenPosition([6, 0])
+
+            editor.insertText("\n")
+            expect(editor.lineForBufferRow(3)).toBe ""
+            expect(editor.lineForBufferRow(4)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+            expect(editor.lineForBufferRow(5)).toBe "    while(items.length > 0) {"
+            expect(editor.lineForBufferRow(6)).toBe "      current = items.shift();"
+            expect(editor.lineForBufferRow(7)).toBe ""
+            expect(editor.lineForBufferRow(8)).toBe "      current < pivot ? left.push(current) : right.push(current);"
+            expect(editor.lineForBufferRow(9)).toBe "    }"
+
+            [cursor1, cursor2] = editor.getCursors()
+            expect(cursor1.getBufferPosition()).toEqual [4,0]
+            expect(cursor2.getBufferPosition()).toEqual [8,0]
+
+    describe ".insertNewlineBelow()", ->
+      describe "when the operation is undone", ->
+        it "places the cursor back at the previous location", ->
+          editor.setCursorBufferPosition([0,2])
           editor.insertNewlineBelow()
-          expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-          expect(buffer.lineForRow(1)).toBe "  "
-          expect(editor.getCursorBufferPosition()).toEqual [1, 2]
-
-      describe ".insertNewlineAbove()", ->
-        describe "when the cursor is on first line", ->
-          it "inserts a newline on the first line and moves the cursor to the first line", ->
-            editor.setCursorBufferPosition([0])
-            editor.insertNewlineAbove()
-            expect(editor.getCursorBufferPosition()).toEqual [0,0]
-            expect(editor.lineForBufferRow(0)).toBe ''
-            expect(editor.lineForBufferRow(1)).toBe 'var quicksort = function () {'
-            expect(editor.buffer.getLineCount()).toBe 14
-
-        describe "when the cursor is not on the first line", ->
-          it "inserts a newline above the current line and moves the cursor to the inserted line", ->
-            editor.setCursorBufferPosition([3,4])
-            editor.insertNewlineAbove()
-            expect(editor.getCursorBufferPosition()).toEqual [3,0]
-            expect(editor.lineForBufferRow(3)).toBe ''
-            expect(editor.lineForBufferRow(4)).toBe '    var pivot = items.shift(), current, left = [], right = [];'
-            expect(editor.buffer.getLineCount()).toBe 14
-
-            editor.undo()
-            expect(editor.getCursorBufferPosition()).toEqual [3,4]
-
-      describe ".backspace()", ->
-        describe "when there is a single cursor", ->
-          changeScreenRangeHandler = null
-
-          beforeEach ->
-            selection = editor.getLastSelection()
-            changeScreenRangeHandler = jasmine.createSpy('changeScreenRangeHandler')
-            selection.on 'screen-range-changed', changeScreenRangeHandler
-
-          describe "when the cursor is on the middle of the line", ->
-            it "removes the character before the cursor", ->
-              editor.setCursorScreenPosition(row: 1, column: 7)
-              expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
-
-              editor.backspace()
-
-              line = buffer.lineForRow(1)
-              expect(line).toBe "  var ort = function(items) {"
-              expect(editor.getCursorScreenPosition()).toEqual {row: 1, column: 6}
-              expect(changeScreenRangeHandler).toHaveBeenCalled()
-              expect(editor.getCursor().isVisible()).toBeTruthy()
-
-          describe "when the cursor is at the beginning of a line", ->
-            it "joins it with the line above", ->
-              originalLine0 = buffer.lineForRow(0)
-              expect(originalLine0).toBe "var quicksort = function () {"
-              expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
-
-              editor.setCursorScreenPosition(row: 1, column: 0)
-              editor.backspace()
-
-              line0 = buffer.lineForRow(0)
-              line1 = buffer.lineForRow(1)
-              expect(line0).toBe "var quicksort = function () {  var sort = function(items) {"
-              expect(line1).toBe "    if (items.length <= 1) return items;"
-              expect(editor.getCursorScreenPosition()).toEqual [0, originalLine0.length]
-
-              expect(changeScreenRangeHandler).toHaveBeenCalled()
-
-          describe "when the cursor is at the first column of the first line", ->
-            it "does nothing, but doesn't raise an error", ->
-              editor.setCursorScreenPosition(row: 0, column: 0)
-              editor.backspace()
-
-          describe "when the cursor is on the first column of a line below a fold", ->
-            it "deletes the folded lines", ->
-              editor.setCursorScreenPosition([4,0])
-              editor.foldCurrentRow()
-              editor.setCursorScreenPosition([5,0])
-              editor.backspace()
-
-              expect(buffer.lineForRow(4)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
-              expect(buffer.lineForRow(4).fold).toBeUndefined()
-
-          describe "when the cursor is in the middle of a line below a fold", ->
-            it "backspaces as normal", ->
-              editor.setCursorScreenPosition([4,0])
-              editor.foldCurrentRow()
-              editor.setCursorScreenPosition([5,5])
-              editor.backspace()
-
-              expect(buffer.lineForRow(7)).toBe "    }"
-              expect(buffer.lineForRow(8)).toBe "    eturn sort(left).concat(pivot).concat(sort(right));"
-
-          describe "when the cursor is on a folded screen line", ->
-            it "deletes all of the folded lines along with the fold", ->
-              editor.setCursorBufferPosition([3, 0])
-              editor.foldCurrentRow()
-              editor.backspace()
-
-              expect(buffer.lineForRow(1)).toBe ""
-              expect(buffer.lineForRow(2)).toBe "  return sort(Array.apply(this, arguments));"
-              expect(editor.getCursorScreenPosition()).toEqual [1, 0]
-
-        describe "when there are multiple cursors", ->
-          describe "when cursors are on the same line", ->
+          expect(editor.getCursorBufferPosition()).toEqual [1,0]
+          editor.undo()
+          expect(editor.getCursorBufferPosition()).toEqual [0,2]
+
+      it "inserts a newline below the cursor's current line, autoindents it, and moves the cursor to the end of the line", ->
+        atom.config.set("editor.autoIndent", true)
+        editor.insertNewlineBelow()
+        expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
+        expect(buffer.lineForRow(1)).toBe "  "
+        expect(editor.getCursorBufferPosition()).toEqual [1, 2]
+
+    describe ".insertNewlineAbove()", ->
+      describe "when the cursor is on first line", ->
+        it "inserts a newline on the first line and moves the cursor to the first line", ->
+          editor.setCursorBufferPosition([0])
+          editor.insertNewlineAbove()
+          expect(editor.getCursorBufferPosition()).toEqual [0,0]
+          expect(editor.lineForBufferRow(0)).toBe ''
+          expect(editor.lineForBufferRow(1)).toBe 'var quicksort = function () {'
+          expect(editor.buffer.getLineCount()).toBe 14
+
+      describe "when the cursor is not on the first line", ->
+        it "inserts a newline above the current line and moves the cursor to the inserted line", ->
+          editor.setCursorBufferPosition([3,4])
+          editor.insertNewlineAbove()
+          expect(editor.getCursorBufferPosition()).toEqual [3,0]
+          expect(editor.lineForBufferRow(3)).toBe ''
+          expect(editor.lineForBufferRow(4)).toBe '    var pivot = items.shift(), current, left = [], right = [];'
+          expect(editor.buffer.getLineCount()).toBe 14
+
+          editor.undo()
+          expect(editor.getCursorBufferPosition()).toEqual [3,4]
+
+    describe ".backspace()", ->
+      describe "when there is a single cursor", ->
+        changeScreenRangeHandler = null
+
+        beforeEach ->
+          selection = editor.getLastSelection()
+          changeScreenRangeHandler = jasmine.createSpy('changeScreenRangeHandler')
+          selection.on 'screen-range-changed', changeScreenRangeHandler
+
+        describe "when the cursor is on the middle of the line", ->
+          it "removes the character before the cursor", ->
+            editor.setCursorScreenPosition(row: 1, column: 7)
+            expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
+
+            editor.backspace()
+
+            line = buffer.lineForRow(1)
+            expect(line).toBe "  var ort = function(items) {"
+            expect(editor.getCursorScreenPosition()).toEqual {row: 1, column: 6}
+            expect(changeScreenRangeHandler).toHaveBeenCalled()
+            expect(editor.getCursor().isVisible()).toBeTruthy()
+
+        describe "when the cursor is at the beginning of a line", ->
+          it "joins it with the line above", ->
+            originalLine0 = buffer.lineForRow(0)
+            expect(originalLine0).toBe "var quicksort = function () {"
+            expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
+
+            editor.setCursorScreenPosition(row: 1, column: 0)
+            editor.backspace()
+
+            line0 = buffer.lineForRow(0)
+            line1 = buffer.lineForRow(1)
+            expect(line0).toBe "var quicksort = function () {  var sort = function(items) {"
+            expect(line1).toBe "    if (items.length <= 1) return items;"
+            expect(editor.getCursorScreenPosition()).toEqual [0, originalLine0.length]
+
+            expect(changeScreenRangeHandler).toHaveBeenCalled()
+
+        describe "when the cursor is at the first column of the first line", ->
+          it "does nothing, but doesn't raise an error", ->
+            editor.setCursorScreenPosition(row: 0, column: 0)
+            editor.backspace()
+
+        describe "when the cursor is on the first column of a line below a fold", ->
+          it "deletes the folded lines", ->
+            editor.setCursorScreenPosition([4,0])
+            editor.foldCurrentRow()
+            editor.setCursorScreenPosition([5,0])
+            editor.backspace()
+
+            expect(buffer.lineForRow(4)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
+            expect(buffer.lineForRow(4).fold).toBeUndefined()
+
+        describe "when the cursor is in the middle of a line below a fold", ->
+          it "backspaces as normal", ->
+            editor.setCursorScreenPosition([4,0])
+            editor.foldCurrentRow()
+            editor.setCursorScreenPosition([5,5])
+            editor.backspace()
+
+            expect(buffer.lineForRow(7)).toBe "    }"
+            expect(buffer.lineForRow(8)).toBe "    eturn sort(left).concat(pivot).concat(sort(right));"
+
+        describe "when the cursor is on a folded screen line", ->
+          it "deletes all of the folded lines along with the fold", ->
+            editor.setCursorBufferPosition([3, 0])
+            editor.foldCurrentRow()
+            editor.backspace()
+
+            expect(buffer.lineForRow(1)).toBe ""
+            expect(buffer.lineForRow(2)).toBe "  return sort(Array.apply(this, arguments));"
+            expect(editor.getCursorScreenPosition()).toEqual [1, 0]
+
+      describe "when there are multiple cursors", ->
+        describe "when cursors are on the same line", ->
+          it "removes the characters preceding each cursor", ->
+            editor.setCursorScreenPosition([3, 13])
+            editor.addCursorAtScreenPosition([3, 38])
+
+            editor.backspace()
+
+            expect(editor.lineForBufferRow(3)).toBe "    var pivo = items.shift(), curren, left = [], right = [];"
+
+            [cursor1, cursor2] = editor.getCursors()
+            expect(cursor1.getBufferPosition()).toEqual [3, 12]
+            expect(cursor2.getBufferPosition()).toEqual [3, 36]
+
+            [selection1, selection2] = editor.getSelections()
+            expect(selection1.isEmpty()).toBeTruthy()
+            expect(selection2.isEmpty()).toBeTruthy()
+
+        describe "when cursors are on different lines", ->
+          describe "when the cursors are in the middle of their lines", ->
             it "removes the characters preceding each cursor", ->
               editor.setCursorScreenPosition([3, 13])
-              editor.addCursorAtScreenPosition([3, 38])
+              editor.addCursorAtScreenPosition([4, 10])

               editor.backspace()

-              expect(editor.lineForBufferRow(3)).toBe "    var pivo = items.shift(), curren, left = [], right = [];"
+              expect(editor.lineForBufferRow(3)).toBe "    var pivo = items.shift(), current, left = [], right = [];"
+              expect(editor.lineForBufferRow(4)).toBe "    whileitems.length > 0) {"

               [cursor1, cursor2] = editor.getCursors()
               expect(cursor1.getBufferPosition()).toEqual [3, 12]
-              expect(cursor2.getBufferPosition()).toEqual [3, 36]
+              expect(cursor2.getBufferPosition()).toEqual [4, 9]

               [selection1, selection2] = editor.getSelections()
               expect(selection1.isEmpty()).toBeTruthy()
               expect(selection2.isEmpty()).toBeTruthy()

-          describe "when cursors are on different lines", ->
-            describe "when the cursors are in the middle of their lines", ->
-              it "removes the characters preceding each cursor", ->
-                editor.setCursorScreenPosition([3, 13])
-                editor.addCursorAtScreenPosition([4, 10])
-
-                editor.backspace()
-
-                expect(editor.lineForBufferRow(3)).toBe "    var pivo = items.shift(), current, left = [], right = [];"
-                expect(editor.lineForBufferRow(4)).toBe "    whileitems.length > 0) {"
-
-                [cursor1, cursor2] = editor.getCursors()
-                expect(cursor1.getBufferPosition()).toEqual [3, 12]
-                expect(cursor2.getBufferPosition()).toEqual [4, 9]
-
-                [selection1, selection2] = editor.getSelections()
-                expect(selection1.isEmpty()).toBeTruthy()
-                expect(selection2.isEmpty()).toBeTruthy()
-
-            describe "when the cursors are on the first column of their lines", ->
-              it "removes the newlines preceding each cursor", ->
-                editor.setCursorScreenPosition([3, 0])
-                editor.addCursorAtScreenPosition([6, 0])
-
-                editor.backspace()
-                expect(editor.lineForBufferRow(2)).toBe "    if (items.length <= 1) return items;    var pivot = items.shift(), current, left = [], right = [];"
-                expect(editor.lineForBufferRow(3)).toBe "    while(items.length > 0) {"
-                expect(editor.lineForBufferRow(4)).toBe "      current = items.shift();      current < pivot ? left.push(current) : right.push(current);"
-                expect(editor.lineForBufferRow(5)).toBe "    }"
-
-                [cursor1, cursor2] = editor.getCursors()
-                expect(cursor1.getBufferPosition()).toEqual [2,40]
-                expect(cursor2.getBufferPosition()).toEqual [4,30]
-
-        describe "when there is a single selection", ->
-          it "deletes the selection, but not the character before it", ->
-            editor.setSelectedBufferRange([[0,5], [0,9]])
+          describe "when the cursors are on the first column of their lines", ->
+            it "removes the newlines preceding each cursor", ->
+              editor.setCursorScreenPosition([3, 0])
+              editor.addCursorAtScreenPosition([6, 0])
+
+              editor.backspace()
+              expect(editor.lineForBufferRow(2)).toBe "    if (items.length <= 1) return items;    var pivot = items.shift(), current, left = [], right = [];"
+              expect(editor.lineForBufferRow(3)).toBe "    while(items.length > 0) {"
+              expect(editor.lineForBufferRow(4)).toBe "      current = items.shift();      current < pivot ? left.push(current) : right.push(current);"
+              expect(editor.lineForBufferRow(5)).toBe "    }"
+
+              [cursor1, cursor2] = editor.getCursors()
+              expect(cursor1.getBufferPosition()).toEqual [2,40]
+              expect(cursor2.getBufferPosition()).toEqual [4,30]
+
+      describe "when there is a single selection", ->
+        it "deletes the selection, but not the character before it", ->
+          editor.setSelectedBufferRange([[0,5], [0,9]])
+          editor.backspace()
+          expect(editor.buffer.lineForRow(0)).toBe 'var qsort = function () {'
+
+        describe "when the selection ends on a folded line", ->
+          it "preserves the fold", ->
+            editor.setSelectedBufferRange([[3,0], [4,0]])
+            editor.foldBufferRow(4)
             editor.backspace()
-            expect(editor.buffer.lineForRow(0)).toBe 'var qsort = function () {'
-
-          describe "when the selection ends on a folded line", ->
-            it "preserves the fold", ->
-              editor.setSelectedBufferRange([[3,0], [4,0]])
-              editor.foldBufferRow(4)
-              editor.backspace()
-
-              expect(buffer.lineForRow(3)).toBe "    while(items.length > 0) {"
-              expect(editor.lineForScreenRow(3).fold).toBeDefined()
-
-        describe "when there are multiple selections", ->
-          it "removes all selected text", ->
-            editor.setSelectedBufferRanges([[[0,4], [0,13]], [[0,16], [0,24]]])
-            editor.backspace()
-            expect(editor.lineForBufferRow(0)).toBe 'var  =  () {'
-
-      describe ".backspaceToBeginningOfWord()", ->
-        describe "when no text is selected", ->
-          it "deletes all text between the cursor and the beginning of the word", ->
-            editor.setCursorBufferPosition([1, 24])
-            editor.addCursorAtBufferPosition([3, 5])
+
+            expect(buffer.lineForRow(3)).toBe "    while(items.length > 0) {"
+            expect(editor.lineForScreenRow(3).fold).toBeDefined()
+
+      describe "when there are multiple selections", ->
+        it "removes all selected text", ->
+          editor.setSelectedBufferRanges([[[0,4], [0,13]], [[0,16], [0,24]]])
+          editor.backspace()
+          expect(editor.lineForBufferRow(0)).toBe 'var  =  () {'
+
+    describe ".backspaceToBeginningOfWord()", ->
+      describe "when no text is selected", ->
+        it "deletes all text between the cursor and the beginning of the word", ->
+          editor.setCursorBufferPosition([1, 24])
+          editor.addCursorAtBufferPosition([3, 5])
+          [cursor1, cursor2] = editor.getCursors()
+
+          editor.backspaceToBeginningOfWord()
+          expect(buffer.lineForRow(1)).toBe '  var sort = function(ems) {'
+          expect(buffer.lineForRow(3)).toBe '    ar pivot = items.shift(), current, left = [], right = [];'
+          expect(cursor1.getBufferPosition()).toEqual [1, 22]
+          expect(cursor2.getBufferPosition()).toEqual [3, 4]
+
+          editor.backspaceToBeginningOfWord()
+          expect(buffer.lineForRow(1)).toBe '  var sort = functionems) {'
+          expect(buffer.lineForRow(2)).toBe '    if (items.length <= 1) return itemsar pivot = items.shift(), current, left = [], right = [];'
+          expect(cursor1.getBufferPosition()).toEqual [1, 21]
+          expect(cursor2.getBufferPosition()).toEqual [2, 39]
+
+          editor.backspaceToBeginningOfWord()
+          expect(buffer.lineForRow(1)).toBe '  var sort = ems) {'
+          expect(buffer.lineForRow(2)).toBe '    if (items.length <= 1) return ar pivot = items.shift(), current, left = [], right = [];'
+          expect(cursor1.getBufferPosition()).toEqual [1, 13]
+          expect(cursor2.getBufferPosition()).toEqual [2, 34]
+
+      describe "when text is selected", ->
+        it "deletes only selected text", ->
+          editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]])
+          editor.backspaceToBeginningOfWord()
+          expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
+          expect(buffer.lineForRow(2)).toBe 'if (items.length <= 1) return items;'
+
+    describe ".backspaceToBeginningOfLine()", ->
+      describe "when no text is selected", ->
+        it "deletes all text between the cursor and the beginning of the line", ->
+          editor.setCursorBufferPosition([1, 24])
+          editor.addCursorAtBufferPosition([2, 5])
+          [cursor1, cursor2] = editor.getCursors()
+
+          editor.backspaceToBeginningOfLine()
+          expect(buffer.lineForRow(1)).toBe 'ems) {'
+          expect(buffer.lineForRow(2)).toBe 'f (items.length <= 1) return items;'
+          expect(cursor1.getBufferPosition()).toEqual [1, 0]
+          expect(cursor2.getBufferPosition()).toEqual [2, 0]
+
+        describe "when at the beginning of the line", ->
+          it "deletes the newline", ->
+            editor.setCursorBufferPosition([2])
+            editor.backspaceToBeginningOfLine()
+            expect(buffer.lineForRow(1)).toBe '  var sort = function(items) {    if (items.length <= 1) return items;'
+
+      describe "when text is selected", ->
+        it "still deletes all text to begginning of the line", ->
+          editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]])
+          editor.backspaceToBeginningOfLine()
+          expect(buffer.lineForRow(1)).toBe 'ems) {'
+          expect(buffer.lineForRow(2)).toBe '    if (items.length <= 1) return items;'
+
+    describe ".delete()", ->
+      describe "when there is a single cursor", ->
+        describe "when the cursor is on the middle of a line", ->
+          it "deletes the character following the cursor", ->
+            editor.setCursorScreenPosition([1, 6])
+            editor.delete()
+            expect(buffer.lineForRow(1)).toBe '  var ort = function(items) {'
+
+        describe "when the cursor is on the end of a line", ->
+          it "joins the line with the following line", ->
+            editor.setCursorScreenPosition([1, buffer.lineForRow(1).length])
+            editor.delete()
+            expect(buffer.lineForRow(1)).toBe '  var sort = function(items) {    if (items.length <= 1) return items;'
+
+        describe "when the cursor is on the last column of the last line", ->
+          it "does nothing, but doesn't raise an error", ->
+            editor.setCursorScreenPosition([12, buffer.lineForRow(12).length])
+            editor.delete()
+            expect(buffer.lineForRow(12)).toBe '};'
+
+        describe "when the cursor is on the end of a line above a fold", ->
+          it "only deletes the lines inside the fold", ->
+            editor.foldBufferRow(4)
+            editor.setCursorScreenPosition([3, Infinity])
+            cursorPositionBefore = editor.getCursorScreenPosition()
+
+            editor.delete()
+
+            expect(buffer.lineForRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+            expect(buffer.lineForRow(4)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
+            expect(editor.getCursorScreenPosition()).toEqual cursorPositionBefore
+
+        describe "when the cursor is in the middle a line above a fold", ->
+          it "deletes as normal", ->
+            editor.foldBufferRow(4)
+            editor.setCursorScreenPosition([3, 4])
+            cursorPositionBefore = editor.getCursorScreenPosition()
+
+            editor.delete()
+
+            expect(buffer.lineForRow(3)).toBe "    ar pivot = items.shift(), current, left = [], right = [];"
+            expect(editor.lineForScreenRow(4).fold).toBeDefined()
+            expect(editor.getCursorScreenPosition()).toEqual [3, 4]
+
+        describe "when the cursor is on a folded line", ->
+          it "removes the lines contained by the fold", ->
+            editor.setSelectedBufferRange([[2, 0], [2, 0]])
+            editor.createFold(2,4)
+            editor.createFold(2,6)
+            oldLine7 = buffer.lineForRow(7)
+            oldLine8 = buffer.lineForRow(8)
+
+            editor.delete()
+            expect(editor.lineForScreenRow(2).text).toBe oldLine7
+            expect(editor.lineForScreenRow(3).text).toBe oldLine8
+
+      describe "when there are multiple cursors", ->
+        describe "when cursors are on the same line", ->
+          it "removes the characters following each cursor", ->
+            editor.setCursorScreenPosition([3, 13])
+            editor.addCursorAtScreenPosition([3, 38])
+
+            editor.delete()
+
+            expect(editor.lineForBufferRow(3)).toBe "    var pivot= items.shift(), current left = [], right = [];"
+
             [cursor1, cursor2] = editor.getCursors()
-
-            editor.backspaceToBeginningOfWord()
-            expect(buffer.lineForRow(1)).toBe '  var sort = function(ems) {'
-            expect(buffer.lineForRow(3)).toBe '    ar pivot = items.shift(), current, left = [], right = [];'
-            expect(cursor1.getBufferPosition()).toEqual [1, 22]
-            expect(cursor2.getBufferPosition()).toEqual [3, 4]
-
-            editor.backspaceToBeginningOfWord()
-            expect(buffer.lineForRow(1)).toBe '  var sort = functionems) {'
-            expect(buffer.lineForRow(2)).toBe '    if (items.length <= 1) return itemsar pivot = items.shift(), current, left = [], right = [];'
-            expect(cursor1.getBufferPosition()).toEqual [1, 21]
-            expect(cursor2.getBufferPosition()).toEqual [2, 39]
-
-            editor.backspaceToBeginningOfWord()
-            expect(buffer.lineForRow(1)).toBe '  var sort = ems) {'
-            expect(buffer.lineForRow(2)).toBe '    if (items.length <= 1) return ar pivot = items.shift(), current, left = [], right = [];'
-            expect(cursor1.getBufferPosition()).toEqual [1, 13]
-            expect(cursor2.getBufferPosition()).toEqual [2, 34]
-
-        describe "when text is selected", ->
-          it "deletes only selected text", ->
-            editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]])
-            editor.backspaceToBeginningOfWord()
-            expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
-            expect(buffer.lineForRow(2)).toBe 'if (items.length <= 1) return items;'
-
-      describe ".backspaceToBeginningOfLine()", ->
-        describe "when no text is selected", ->
-          it "deletes all text between the cursor and the beginning of the line", ->
-            editor.setCursorBufferPosition([1, 24])
-            editor.addCursorAtBufferPosition([2, 5])
-            [cursor1, cursor2] = editor.getCursors()
-
-            editor.backspaceToBeginningOfLine()
-            expect(buffer.lineForRow(1)).toBe 'ems) {'
-            expect(buffer.lineForRow(2)).toBe 'f (items.length <= 1) return items;'
-            expect(cursor1.getBufferPosition()).toEqual [1, 0]
-            expect(cursor2.getBufferPosition()).toEqual [2, 0]
-
-          describe "when at the beginning of the line", ->
-            it "deletes the newline", ->
-              editor.setCursorBufferPosition([2])
-              editor.backspaceToBeginningOfLine()
-              expect(buffer.lineForRow(1)).toBe '  var sort = function(items) {    if (items.length <= 1) return items;'
-
-        describe "when text is selected", ->
-          it "still deletes all text to begginning of the line", ->
-            editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]])
-            editor.backspaceToBeginningOfLine()
-            expect(buffer.lineForRow(1)).toBe 'ems) {'
-            expect(buffer.lineForRow(2)).toBe '    if (items.length <= 1) return items;'
-
-      describe ".delete()", ->
-        describe "when there is a single cursor", ->
-          describe "when the cursor is on the middle of a line", ->
-            it "deletes the character following the cursor", ->
-              editor.setCursorScreenPosition([1, 6])
-              editor.delete()
-              expect(buffer.lineForRow(1)).toBe '  var ort = function(items) {'
-
-          describe "when the cursor is on the end of a line", ->
-            it "joins the line with the following line", ->
-              editor.setCursorScreenPosition([1, buffer.lineForRow(1).length])
-              editor.delete()
-              expect(buffer.lineForRow(1)).toBe '  var sort = function(items) {    if (items.length <= 1) return items;'
-
-          describe "when the cursor is on the last column of the last line", ->
-            it "does nothing, but doesn't raise an error", ->
-              editor.setCursorScreenPosition([12, buffer.lineForRow(12).length])
-              editor.delete()
-              expect(buffer.lineForRow(12)).toBe '};'
-
-          describe "when the cursor is on the end of a line above a fold", ->
-            it "only deletes the lines inside the fold", ->
-              editor.foldBufferRow(4)
-              editor.setCursorScreenPosition([3, Infinity])
-              cursorPositionBefore = editor.getCursorScreenPosition()
-
-              editor.delete()
-
-              expect(buffer.lineForRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
-              expect(buffer.lineForRow(4)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
-              expect(editor.getCursorScreenPosition()).toEqual cursorPositionBefore
-
-          describe "when the cursor is in the middle a line above a fold", ->
-            it "deletes as normal", ->
-              editor.foldBufferRow(4)
-              editor.setCursorScreenPosition([3, 4])
-              cursorPositionBefore = editor.getCursorScreenPosition()
-
-              editor.delete()
-
-              expect(buffer.lineForRow(3)).toBe "    ar pivot = items.shift(), current, left = [], right = [];"
-              expect(editor.lineForScreenRow(4).fold).toBeDefined()
-              expect(editor.getCursorScreenPosition()).toEqual [3, 4]
-
-          describe "when the cursor is on a folded line", ->
-            it "removes the lines contained by the fold", ->
-              editor.setSelectedBufferRange([[2, 0], [2, 0]])
-              editor.createFold(2,4)
-              editor.createFold(2,6)
-              oldLine7 = buffer.lineForRow(7)
-              oldLine8 = buffer.lineForRow(8)
-
-              editor.delete()
-              expect(editor.lineForScreenRow(2).text).toBe oldLine7
-              expect(editor.lineForScreenRow(3).text).toBe oldLine8
-
-        describe "when there are multiple cursors", ->
-          describe "when cursors are on the same line", ->
+            expect(cursor1.getBufferPosition()).toEqual [3, 13]
+            expect(cursor2.getBufferPosition()).toEqual [3, 37]
+
+            [selection1, selection2] = editor.getSelections()
+            expect(selection1.isEmpty()).toBeTruthy()
+            expect(selection2.isEmpty()).toBeTruthy()
+
+        describe "when cursors are on different lines", ->
+          describe "when the cursors are in the middle of the lines", ->
             it "removes the characters following each cursor", ->
               editor.setCursorScreenPosition([3, 13])
-              editor.addCursorAtScreenPosition([3, 38])
+              editor.addCursorAtScreenPosition([4, 10])

               editor.delete()

-              expect(editor.lineForBufferRow(3)).toBe "    var pivot= items.shift(), current left = [], right = [];"
+              expect(editor.lineForBufferRow(3)).toBe "    var pivot= items.shift(), current, left = [], right = [];"
+              expect(editor.lineForBufferRow(4)).toBe "    while(tems.length > 0) {"

               [cursor1, cursor2] = editor.getCursors()
               expect(cursor1.getBufferPosition()).toEqual [3, 13]
-              expect(cursor2.getBufferPosition()).toEqual [3, 37]
+              expect(cursor2.getBufferPosition()).toEqual [4, 10]

               [selection1, selection2] = editor.getSelections()
               expect(selection1.isEmpty()).toBeTruthy()
               expect(selection2.isEmpty()).toBeTruthy()

-          describe "when cursors are on different lines", ->
-            describe "when the cursors are in the middle of the lines", ->
-              it "removes the characters following each cursor", ->
-                editor.setCursorScreenPosition([3, 13])
-                editor.addCursorAtScreenPosition([4, 10])
-
-                editor.delete()
-
-                expect(editor.lineForBufferRow(3)).toBe "    var pivot= items.shift(), current, left = [], right = [];"
-                expect(editor.lineForBufferRow(4)).toBe "    while(tems.length > 0) {"
-
-                [cursor1, cursor2] = editor.getCursors()
-                expect(cursor1.getBufferPosition()).toEqual [3, 13]
-                expect(cursor2.getBufferPosition()).toEqual [4, 10]
-
-                [selection1, selection2] = editor.getSelections()
-                expect(selection1.isEmpty()).toBeTruthy()
-                expect(selection2.isEmpty()).toBeTruthy()
-
-            describe "when the cursors are at the end of their lines", ->
-              it "removes the newlines following each cursor", ->
-                editor.setCursorScreenPosition([0, 29])
-                editor.addCursorAtScreenPosition([1, 30])
-
-                editor.delete()
-
-                expect(editor.lineForBufferRow(0)).toBe "var quicksort = function () {  var sort = function(items) {    if (items.length <= 1) return items;"
-
-                [cursor1, cursor2] = editor.getCursors()
-                expect(cursor1.getBufferPosition()).toEqual [0,29]
-                expect(cursor2.getBufferPosition()).toEqual [0,59]
-
-        describe "when there is a single selection", ->
-          it "deletes the selection, but not the character following it", ->
-            editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]])
+          describe "when the cursors are at the end of their lines", ->
+            it "removes the newlines following each cursor", ->
+              editor.setCursorScreenPosition([0, 29])
+              editor.addCursorAtScreenPosition([1, 30])
+
+              editor.delete()
+
+              expect(editor.lineForBufferRow(0)).toBe "var quicksort = function () {  var sort = function(items) {    if (items.length <= 1) return items;"
+
+              [cursor1, cursor2] = editor.getCursors()
+              expect(cursor1.getBufferPosition()).toEqual [0,29]
+              expect(cursor2.getBufferPosition()).toEqual [0,59]
+
+      describe "when there is a single selection", ->
+        it "deletes the selection, but not the character following it", ->
+          editor.setSelectedBufferRanges([[[1, 24], [1, 27]], [[2, 0], [2, 4]]])
+          editor.delete()
+          expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
+          expect(buffer.lineForRow(2)).toBe 'if (items.length <= 1) return items;'
+          expect(editor.getSelection().isEmpty()).toBeTruthy()
+
+      describe "when there are multiple selections", ->
+        describe "when selections are on the same line", ->
+          it "removes all selected text", ->
+            editor.setSelectedBufferRanges([[[0,4], [0,13]], [[0,16], [0,24]]])
             editor.delete()
-            expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
-            expect(buffer.lineForRow(2)).toBe 'if (items.length <= 1) return items;'
-            expect(editor.getSelection().isEmpty()).toBeTruthy()
-
-        describe "when there are multiple selections", ->
-          describe "when selections are on the same line", ->
-            it "removes all selected text", ->
-              editor.setSelectedBufferRanges([[[0,4], [0,13]], [[0,16], [0,24]]])
-              editor.delete()
-              expect(editor.lineForBufferRow(0)).toBe 'var  =  () {'
-
-      describe ".deleteToEndOfWord()", ->
-        describe "when no text is selected", ->
-          it "deletes to the end of the word", ->
-            editor.setCursorBufferPosition([1, 24])
-            editor.addCursorAtBufferPosition([2, 5])
-            [cursor1, cursor2] = editor.getCursors()
-
-            editor.deleteToEndOfWord()
-            expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
-            expect(buffer.lineForRow(2)).toBe '    i (items.length <= 1) return items;'
-            expect(cursor1.getBufferPosition()).toEqual [1, 24]
-            expect(cursor2.getBufferPosition()).toEqual [2, 5]
-
-            editor.deleteToEndOfWord()
-            expect(buffer.lineForRow(1)).toBe '  var sort = function(it {'
-            expect(buffer.lineForRow(2)).toBe '    iitems.length <= 1) return items;'
-            expect(cursor1.getBufferPosition()).toEqual [1, 24]
-            expect(cursor2.getBufferPosition()).toEqual [2, 5]
-
-        describe "when text is selected", ->
-          it "deletes only selected text", ->
-            editor.setSelectedBufferRange([[1, 24], [1, 27]])
-            editor.deleteToEndOfWord()
-            expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
-
-      describe ".indent()", ->
-        describe "when the selection is empty", ->
-          describe "when autoIndent is disabled", ->
-            describe "if 'softTabs' is true (the default)", ->
-              it "inserts 'tabLength' spaces into the buffer", ->
-                tabRegex = new RegExp("^[ ]{#{editor.getTabLength()}}")
-                expect(buffer.lineForRow(0)).not.toMatch(tabRegex)
-                editor.indent()
-                expect(buffer.lineForRow(0)).toMatch(tabRegex)
-
-            describe "if 'softTabs' is false", ->
-              it "insert a \t into the buffer", ->
+            expect(editor.lineForBufferRow(0)).toBe 'var  =  () {'
+
+    describe ".deleteToEndOfWord()", ->
+      describe "when no text is selected", ->
+        it "deletes to the end of the word", ->
+          editor.setCursorBufferPosition([1, 24])
+          editor.addCursorAtBufferPosition([2, 5])
+          [cursor1, cursor2] = editor.getCursors()
+
+          editor.deleteToEndOfWord()
+          expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
+          expect(buffer.lineForRow(2)).toBe '    i (items.length <= 1) return items;'
+          expect(cursor1.getBufferPosition()).toEqual [1, 24]
+          expect(cursor2.getBufferPosition()).toEqual [2, 5]
+
+          editor.deleteToEndOfWord()
+          expect(buffer.lineForRow(1)).toBe '  var sort = function(it {'
+          expect(buffer.lineForRow(2)).toBe '    iitems.length <= 1) return items;'
+          expect(cursor1.getBufferPosition()).toEqual [1, 24]
+          expect(cursor2.getBufferPosition()).toEqual [2, 5]
+
+      describe "when text is selected", ->
+        it "deletes only selected text", ->
+          editor.setSelectedBufferRange([[1, 24], [1, 27]])
+          editor.deleteToEndOfWord()
+          expect(buffer.lineForRow(1)).toBe '  var sort = function(it) {'
+
+    describe ".indent()", ->
+      describe "when the selection is empty", ->
+        describe "when autoIndent is disabled", ->
+          describe "if 'softTabs' is true (the default)", ->
+            it "inserts 'tabLength' spaces into the buffer", ->
+              tabRegex = new RegExp("^[ ]{#{editor.getTabLength()}}")
+              expect(buffer.lineForRow(0)).not.toMatch(tabRegex)
+              editor.indent()
+              expect(buffer.lineForRow(0)).toMatch(tabRegex)
+
+          describe "if 'softTabs' is false", ->
+            it "insert a \t into the buffer", ->
+              editor.setSoftTabs(false)
+              expect(buffer.lineForRow(0)).not.toMatch(/^\t/)
+              editor.indent()
+              expect(buffer.lineForRow(0)).toMatch(/^\t/)
+
+        describe "when autoIndent is enabled", ->
+          describe "when the cursor's column is less than the suggested level of indentation", ->
+            describe "when 'softTabs' is true (the default)", ->
+              it "moves the cursor to the end of the leading whitespace and inserts enough whitespace to bring the line to the suggested level of indentaion", ->
+                buffer.insert([5, 0], "  \n")
+                editor.setCursorBufferPosition [5, 0]
+                editor.indent(autoIndent: true)
+                expect(buffer.lineForRow(5)).toMatch /^\s+$/
+                expect(buffer.lineForRow(5).length).toBe 6
+                expect(editor.getCursorBufferPosition()).toEqual [5, 6]
+
+            describe "when 'softTabs' is false", ->
+              it "moves the cursor to the end of the leading whitespace and inserts enough tabs to bring the line to the suggested level of indentaion", ->
+                convertToHardTabs(buffer)
                 editor.setSoftTabs(false)
-                expect(buffer.lineForRow(0)).not.toMatch(/^\t/)
-                editor.indent()
-                expect(buffer.lineForRow(0)).toMatch(/^\t/)
-
-          describe "when autoIndent is enabled", ->
-            describe "when the cursor's column is less than the suggested level of indentation", ->
-              describe "when 'softTabs' is true (the default)", ->
-                it "moves the cursor to the end of the leading whitespace and inserts enough whitespace to bring the line to the suggested level of indentaion", ->
-                  buffer.insert([5, 0], "  \n")
-                  editor.setCursorBufferPosition [5, 0]
-                  editor.indent(autoIndent: true)
-                  expect(buffer.lineForRow(5)).toMatch /^\s+$/
-                  expect(buffer.lineForRow(5).length).toBe 6
-                  expect(editor.getCursorBufferPosition()).toEqual [5, 6]
-
-              describe "when 'softTabs' is false", ->
-                it "moves the cursor to the end of the leading whitespace and inserts enough tabs to bring the line to the suggested level of indentaion", ->
-                  convertToHardTabs(buffer)
-                  editor.setSoftTabs(false)
-                  buffer.insert([5, 0], "\t\n")
-                  editor.setCursorBufferPosition [5, 0]
-                  editor.indent(autoIndent: true)
-                  expect(buffer.lineForRow(5)).toMatch /^\t\t\t$/
-                  expect(editor.getCursorBufferPosition()).toEqual [5, 3]
-
-            describe "when the line's indent level is greater than the suggested level of indentation", ->
-              describe "when 'softTabs' is true (the default)", ->
-                it "moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", ->
-                  buffer.insert([7, 0], "      \n")
-                  editor.setCursorBufferPosition [7, 2]
-                  editor.indent(autoIndent: true)
-                  expect(buffer.lineForRow(7)).toMatch /^\s+$/
-                  expect(buffer.lineForRow(7).length).toBe 8
-                  expect(editor.getCursorBufferPosition()).toEqual [7, 8]
-
-              describe "when 'softTabs' is false", ->
-                it "moves the cursor to the end of the leading whitespace and inserts \t into the buffer", ->
-                  convertToHardTabs(buffer)
-                  editor.setSoftTabs(false)
-                  buffer.insert([7, 0], "\t\t\t\n")
-                  editor.setCursorBufferPosition [7, 1]
-                  editor.indent(autoIndent: true)
-                  expect(buffer.lineForRow(7)).toMatch /^\t\t\t\t$/
-                  expect(editor.getCursorBufferPosition()).toEqual [7, 4]
-
-        describe "when the selection is not empty", ->
-          it "indents the selected lines", ->
-            editor.setSelectedBufferRange([[0, 0], [10, 0]])
-            selection = editor.getSelection()
-            spyOn(selection, "indentSelectedRows")
-            editor.indent()
-            expect(selection.indentSelectedRows).toHaveBeenCalled()
-
-        describe "if editor.softTabs is false", ->
-          it "inserts a tab character into the buffer", ->
+                buffer.insert([5, 0], "\t\n")
+                editor.setCursorBufferPosition [5, 0]
+                editor.indent(autoIndent: true)
+                expect(buffer.lineForRow(5)).toMatch /^\t\t\t$/
+                expect(editor.getCursorBufferPosition()).toEqual [5, 3]
+
+          describe "when the line's indent level is greater than the suggested level of indentation", ->
+            describe "when 'softTabs' is true (the default)", ->
+              it "moves the cursor to the end of the leading whitespace and inserts 'tabLength' spaces into the buffer", ->
+                buffer.insert([7, 0], "      \n")
+                editor.setCursorBufferPosition [7, 2]
+                editor.indent(autoIndent: true)
+                expect(buffer.lineForRow(7)).toMatch /^\s+$/
+                expect(buffer.lineForRow(7).length).toBe 8
+                expect(editor.getCursorBufferPosition()).toEqual [7, 8]
+
+            describe "when 'softTabs' is false", ->
+              it "moves the cursor to the end of the leading whitespace and inserts \t into the buffer", ->
+                convertToHardTabs(buffer)
+                editor.setSoftTabs(false)
+                buffer.insert([7, 0], "\t\t\t\n")
+                editor.setCursorBufferPosition [7, 1]
+                editor.indent(autoIndent: true)
+                expect(buffer.lineForRow(7)).toMatch /^\t\t\t\t$/
+                expect(editor.getCursorBufferPosition()).toEqual [7, 4]
+
+      describe "when the selection is not empty", ->
+        it "indents the selected lines", ->
+          editor.setSelectedBufferRange([[0, 0], [10, 0]])
+          selection = editor.getSelection()
+          spyOn(selection, "indentSelectedRows")
+          editor.indent()
+          expect(selection.indentSelectedRows).toHaveBeenCalled()
+
+      describe "if editor.softTabs is false", ->
+        it "inserts a tab character into the buffer", ->
+          editor.setSoftTabs(false)
+          expect(buffer.lineForRow(0)).not.toMatch(/^\t/)
+          editor.indent()
+          expect(buffer.lineForRow(0)).toMatch(/^\t/)
+          expect(editor.getCursorBufferPosition()).toEqual [0, 1]
+          expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength()]
+
+          editor.indent()
+          expect(buffer.lineForRow(0)).toMatch(/^\t\t/)
+          expect(editor.getCursorBufferPosition()).toEqual [0, 2]
+          expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength() * 2]
+
+    describe "clipboard operations", ->
+      beforeEach ->
+        editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
+
+      describe ".cutSelectedText()", ->
+        it "removes the selected text from the buffer and places it on the clipboard", ->
+          editor.cutSelectedText()
+          expect(buffer.lineForRow(0)).toBe "var  = function () {"
+          expect(buffer.lineForRow(1)).toBe "  var  = function(items) {"
+
+          expect(clipboard.readText()).toBe 'quicksort\nsort'
+
+      describe ".cutToEndOfLine()", ->
+        describe "when soft wrap is on", ->
+          it "cuts up to the end of the line", ->
+            editor.setSoftWrap(true)
+            editor.setEditorWidthInChars(10)
+            editor.setCursorScreenPosition([2, 2])
+            editor.cutToEndOfLine()
+            expect(editor.lineForScreenRow(2).text).toBe '=  () {'
+
+        describe "when soft wrap is off", ->
+          describe "when nothing is selected", ->
+            it "cuts up to the end of the line", ->
+              editor.setCursorBufferPosition([2, 20])
+              editor.addCursorAtBufferPosition([3, 20])
+              editor.cutToEndOfLine()
+              expect(buffer.lineForRow(2)).toBe '    if (items.length'
+              expect(buffer.lineForRow(3)).toBe '    var pivot = item'
+              expect(atom.clipboard.read()).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
+
+          describe "when text is selected", ->
+            it "only cuts the selected text, not to the end of the line", ->
+              editor.setSelectedBufferRanges([[[2,20], [2, 30]], [[3, 20], [3, 20]]])
+
+              editor.cutToEndOfLine()
+
+              expect(buffer.lineForRow(2)).toBe '    if (items.lengthurn items;'
+              expect(buffer.lineForRow(3)).toBe '    var pivot = item'
+              expect(atom.clipboard.read()).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
+
+      describe ".copySelectedText()", ->
+        it "copies selected text onto the clipboard", ->
+          editor.copySelectedText()
+          expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
+          expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
+          expect(clipboard.readText()).toBe 'quicksort\nsort'
+
+      describe ".pasteText()", ->
+        it "pastes text into the buffer", ->
+          atom.clipboard.write('first')
+          editor.pasteText()
+          expect(editor.buffer.lineForRow(0)).toBe "var first = function () {"
+          expect(buffer.lineForRow(1)).toBe "  var first = function(items) {"
+
+    describe ".indentSelectedRows()", ->
+      describe "when nothing is selected", ->
+        describe "when softTabs is enabled", ->
+          it "indents line and retains selection", ->
+            editor.setSelectedBufferRange([[0,3], [0,3]])
+            editor.indentSelectedRows()
+            expect(buffer.lineForRow(0)).toBe "  var quicksort = function () {"
+            expect(editor.getSelectedBufferRange()).toEqual [[0, 3 + editor.getTabLength()], [0, 3 + editor.getTabLength()]]
+
+        describe "when softTabs is disabled", ->
+          it "indents line and retains selection", ->
+            convertToHardTabs(buffer)
             editor.setSoftTabs(false)
-            expect(buffer.lineForRow(0)).not.toMatch(/^\t/)
-            editor.indent()
-            expect(buffer.lineForRow(0)).toMatch(/^\t/)
-            expect(editor.getCursorBufferPosition()).toEqual [0, 1]
-            expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength()]
-
-            editor.indent()
-            expect(buffer.lineForRow(0)).toMatch(/^\t\t/)
-            expect(editor.getCursorBufferPosition()).toEqual [0, 2]
-            expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength() * 2]
-
-      describe "clipboard operations", ->
-        beforeEach ->
-          editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
-
-        describe ".cutSelectedText()", ->
-          it "removes the selected text from the buffer and places it on the clipboard", ->
-            editor.cutSelectedText()
-            expect(buffer.lineForRow(0)).toBe "var  = function () {"
-            expect(buffer.lineForRow(1)).toBe "  var  = function(items) {"
-
-            expect(clipboard.readText()).toBe 'quicksort\nsort'
-
-        describe ".cutToEndOfLine()", ->
-          describe "when soft wrap is on", ->
-            it "cuts up to the end of the line", ->
-              editor.setSoftWrap(true)
-              editor.setEditorWidthInChars(10)
-              editor.setCursorScreenPosition([2, 2])
-              editor.cutToEndOfLine()
-              expect(editor.lineForScreenRow(2).text).toBe '=  () {'
-
-          describe "when soft wrap is off", ->
-            describe "when nothing is selected", ->
-              it "cuts up to the end of the line", ->
-                editor.setCursorBufferPosition([2, 20])
-                editor.addCursorAtBufferPosition([3, 20])
-                editor.cutToEndOfLine()
-                expect(buffer.lineForRow(2)).toBe '    if (items.length'
-                expect(buffer.lineForRow(3)).toBe '    var pivot = item'
-                expect(atom.clipboard.read()).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
-
-            describe "when text is selected", ->
-              it "only cuts the selected text, not to the end of the line", ->
-                editor.setSelectedBufferRanges([[[2,20], [2, 30]], [[3, 20], [3, 20]]])
-
-                editor.cutToEndOfLine()
-
-                expect(buffer.lineForRow(2)).toBe '    if (items.lengthurn items;'
-                expect(buffer.lineForRow(3)).toBe '    var pivot = item'
-                expect(atom.clipboard.read()).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
-
-        describe ".copySelectedText()", ->
-          it "copies selected text onto the clipboard", ->
-            editor.copySelectedText()
-            expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-            expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
-            expect(clipboard.readText()).toBe 'quicksort\nsort'
-
-        describe ".pasteText()", ->
-          it "pastes text into the buffer", ->
-            atom.clipboard.write('first')
-            editor.pasteText()
-            expect(editor.buffer.lineForRow(0)).toBe "var first = function () {"
-            expect(buffer.lineForRow(1)).toBe "  var first = function(items) {"
-
-      describe ".indentSelectedRows()", ->
-        describe "when nothing is selected", ->
-          describe "when softTabs is enabled", ->
-            it "indents line and retains selection", ->
-              editor.setSelectedBufferRange([[0,3], [0,3]])
-              editor.indentSelectedRows()
-              expect(buffer.lineForRow(0)).toBe "  var quicksort = function () {"
-              expect(editor.getSelectedBufferRange()).toEqual [[0, 3 + editor.getTabLength()], [0, 3 + editor.getTabLength()]]
-
-          describe "when softTabs is disabled", ->
-            it "indents line and retains selection", ->
-              convertToHardTabs(buffer)
-              editor.setSoftTabs(false)
-              editor.setSelectedBufferRange([[0,3], [0,3]])
-              editor.indentSelectedRows()
-              expect(buffer.lineForRow(0)).toBe "\tvar quicksort = function () {"
-              expect(editor.getSelectedBufferRange()).toEqual [[0, 3 + 1], [0, 3 + 1]]
-
-        describe "when one line is selected", ->
-          describe "when softTabs is enabled", ->
-            it "indents line and retains selection", ->
-              editor.setSelectedBufferRange([[0,4], [0,14]])
-              editor.indentSelectedRows()
-              expect(buffer.lineForRow(0)).toBe "#{editor.getTabText()}var quicksort = function () {"
-              expect(editor.getSelectedBufferRange()).toEqual [[0, 4 + editor.getTabLength()], [0, 14 + editor.getTabLength()]]
-
-          describe "when softTabs is disabled", ->
-            it "indents line and retains selection", ->
-              convertToHardTabs(buffer)
-              editor.setSoftTabs(false)
-              editor.setSelectedBufferRange([[0,4], [0,14]])
-              editor.indentSelectedRows()
-              expect(buffer.lineForRow(0)).toBe "\tvar quicksort = function () {"
-              expect(editor.getSelectedBufferRange()).toEqual [[0, 4 + 1], [0, 14 + 1]]
-
-        describe "when multiple lines are selected", ->
-          describe "when softTabs is enabled", ->
-            it "indents selected lines (that are not empty) and retains selection", ->
-              editor.setSelectedBufferRange([[9,1], [11,15]])
-              editor.indentSelectedRows()
-              expect(buffer.lineForRow(9)).toBe "    };"
-              expect(buffer.lineForRow(10)).toBe ""
-              expect(buffer.lineForRow(11)).toBe "    return sort(Array.apply(this, arguments));"
-              expect(editor.getSelectedBufferRange()).toEqual [[9, 1 + editor.getTabLength()], [11, 15 + editor.getTabLength()]]
-
-            it "does not indent the last row if the selection ends at column 0", ->
-              editor.setSelectedBufferRange([[9,1], [11,0]])
-              editor.indentSelectedRows()
-              expect(buffer.lineForRow(9)).toBe "    };"
-              expect(buffer.lineForRow(10)).toBe ""
-              expect(buffer.lineForRow(11)).toBe "  return sort(Array.apply(this, arguments));"
-              expect(editor.getSelectedBufferRange()).toEqual [[9, 1 + editor.getTabLength()], [11, 0]]
-
-          describe "when softTabs is disabled", ->
-            it "indents selected lines (that are not empty) and retains selection", ->
-              convertToHardTabs(buffer)
-              editor.setSoftTabs(false)
-              editor.setSelectedBufferRange([[9,1], [11,15]])
-              editor.indentSelectedRows()
-              expect(buffer.lineForRow(9)).toBe "\t\t};"
-              expect(buffer.lineForRow(10)).toBe ""
-              expect(buffer.lineForRow(11)).toBe "\t\treturn sort(Array.apply(this, arguments));"
-              expect(editor.getSelectedBufferRange()).toEqual [[9, 1 + 1], [11, 15 + 1]]
-
-      describe ".outdentSelectedRows()", ->
-        describe "when nothing is selected", ->
-          it "outdents line and retains selection", ->
-            editor.setSelectedBufferRange([[1,3], [1,3]])
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
-            expect(editor.getSelectedBufferRange()).toEqual [[1, 3 - editor.getTabLength()], [1, 3 - editor.getTabLength()]]
-
-          it "outdents when indent is less than a tab length", ->
-            editor.insertText(' ')
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-
-          it "outdents a single hard tab when indent is multiple hard tabs and and the session is using soft tabs", ->
-            editor.insertText('\t\t')
-            editor.outdentSelectedRows()
+            editor.setSelectedBufferRange([[0,3], [0,3]])
+            editor.indentSelectedRows()
             expect(buffer.lineForRow(0)).toBe "\tvar quicksort = function () {"
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-
-          it "outdents when a mix of hard tabs and soft tabs are used", ->
-            editor.insertText('\t   ')
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(0)).toBe "   var quicksort = function () {"
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(0)).toBe " var quicksort = function () {"
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-
-        describe "when one line is selected", ->
-          it "outdents line and retains editor", ->
-            editor.setSelectedBufferRange([[1,4], [1,14]])
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
-            expect(editor.getSelectedBufferRange()).toEqual [[1, 4 - editor.getTabLength()], [1, 14 - editor.getTabLength()]]
-
-        describe "when multiple lines are selected", ->
-          it "outdents selected lines and retains editor", ->
-            editor.setSelectedBufferRange([[0,1], [3,15]])
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-            expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
-            expect(buffer.lineForRow(2)).toBe "  if (items.length <= 1) return items;"
-            expect(buffer.lineForRow(3)).toBe "  var pivot = items.shift(), current, left = [], right = [];"
-            expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [3, 15 - editor.getTabLength()]]
-
-          it "does not outdent the last line of the selection if it ends at column 0", ->
-            editor.setSelectedBufferRange([[0,1], [3,0]])
-            editor.outdentSelectedRows()
-            expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-            expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
-            expect(buffer.lineForRow(2)).toBe "  if (items.length <= 1) return items;"
-            expect(buffer.lineForRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
-
-            expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [3, 0]]
-
-      describe ".toggleLineCommentsInSelection()", ->
-        it "toggles comments on the selected lines", ->
-          editor.setSelectedBufferRange([[4, 5], [7, 5]])
-          editor.toggleLineCommentsInSelection()
-
-          expect(buffer.lineForRow(4)).toBe "    // while(items.length > 0) {"
-          expect(buffer.lineForRow(5)).toBe "    //   current = items.shift();"
-          expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
-          expect(buffer.lineForRow(7)).toBe "    // }"
-          expect(editor.getSelectedBufferRange()).toEqual [[4, 8], [7, 8]]
-
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(4)).toBe "    while(items.length > 0) {"
-          expect(buffer.lineForRow(5)).toBe "      current = items.shift();"
-          expect(buffer.lineForRow(6)).toBe "      current < pivot ? left.push(current) : right.push(current);"
-          expect(buffer.lineForRow(7)).toBe "    }"
-
-        it "does not comment the last line of a non-empty selection if it ends at column 0", ->
-          editor.setSelectedBufferRange([[4, 5], [7, 0]])
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(4)).toBe "    // while(items.length > 0) {"
-          expect(buffer.lineForRow(5)).toBe "    //   current = items.shift();"
-          expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
-          expect(buffer.lineForRow(7)).toBe "    }"
-
-        it "uncomments lines if all lines match the comment regex", ->
-          editor.setSelectedBufferRange([[0, 0], [0, 1]])
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
-
-          editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(0)).toBe "// // var quicksort = function () {"
-          expect(buffer.lineForRow(1)).toBe "//   var sort = function(items) {"
-          expect(buffer.lineForRow(2)).toBe "//     if (items.length <= 1) return items;"
-
-          editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
-          expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
-          expect(buffer.lineForRow(2)).toBe "    if (items.length <= 1) return items;"
-
-          editor.setSelectedBufferRange([[0, 0], [0, Infinity]])
-          editor.toggleLineCommentsInSelection()
+            expect(editor.getSelectedBufferRange()).toEqual [[0, 3 + 1], [0, 3 + 1]]
+
+      describe "when one line is selected", ->
+        describe "when softTabs is enabled", ->
+          it "indents line and retains selection", ->
+            editor.setSelectedBufferRange([[0,4], [0,14]])
+            editor.indentSelectedRows()
+            expect(buffer.lineForRow(0)).toBe "#{editor.getTabText()}var quicksort = function () {"
+            expect(editor.getSelectedBufferRange()).toEqual [[0, 4 + editor.getTabLength()], [0, 14 + editor.getTabLength()]]
+
+        describe "when softTabs is disabled", ->
+          it "indents line and retains selection", ->
+            convertToHardTabs(buffer)
+            editor.setSoftTabs(false)
+            editor.setSelectedBufferRange([[0,4], [0,14]])
+            editor.indentSelectedRows()
+            expect(buffer.lineForRow(0)).toBe "\tvar quicksort = function () {"
+            expect(editor.getSelectedBufferRange()).toEqual [[0, 4 + 1], [0, 14 + 1]]
+
+      describe "when multiple lines are selected", ->
+        describe "when softTabs is enabled", ->
+          it "indents selected lines (that are not empty) and retains selection", ->
+            editor.setSelectedBufferRange([[9,1], [11,15]])
+            editor.indentSelectedRows()
+            expect(buffer.lineForRow(9)).toBe "    };"
+            expect(buffer.lineForRow(10)).toBe ""
+            expect(buffer.lineForRow(11)).toBe "    return sort(Array.apply(this, arguments));"
+            expect(editor.getSelectedBufferRange()).toEqual [[9, 1 + editor.getTabLength()], [11, 15 + editor.getTabLength()]]
+
+          it "does not indent the last row if the selection ends at column 0", ->
+            editor.setSelectedBufferRange([[9,1], [11,0]])
+            editor.indentSelectedRows()
+            expect(buffer.lineForRow(9)).toBe "    };"
+            expect(buffer.lineForRow(10)).toBe ""
+            expect(buffer.lineForRow(11)).toBe "  return sort(Array.apply(this, arguments));"
+            expect(editor.getSelectedBufferRange()).toEqual [[9, 1 + editor.getTabLength()], [11, 0]]
+
+        describe "when softTabs is disabled", ->
+          it "indents selected lines (that are not empty) and retains selection", ->
+            convertToHardTabs(buffer)
+            editor.setSoftTabs(false)
+            editor.setSelectedBufferRange([[9,1], [11,15]])
+            editor.indentSelectedRows()
+            expect(buffer.lineForRow(9)).toBe "\t\t};"
+            expect(buffer.lineForRow(10)).toBe ""
+            expect(buffer.lineForRow(11)).toBe "\t\treturn sort(Array.apply(this, arguments));"
+            expect(editor.getSelectedBufferRange()).toEqual [[9, 1 + 1], [11, 15 + 1]]
+
+    describe ".outdentSelectedRows()", ->
+      describe "when nothing is selected", ->
+        it "outdents line and retains selection", ->
+          editor.setSelectedBufferRange([[1,3], [1,3]])
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
+          expect(editor.getSelectedBufferRange()).toEqual [[1, 3 - editor.getTabLength()], [1, 3 - editor.getTabLength()]]
+
+        it "outdents when indent is less than a tab length", ->
+          editor.insertText(' ')
+          editor.outdentSelectedRows()
           expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"

-        it "uncomments commented lines separated by an empty line", ->
-          editor.setSelectedBufferRange([[0, 0], [1, Infinity]])
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
-          expect(buffer.lineForRow(1)).toBe "//   var sort = function(items) {"
-
-          buffer.insert([0, Infinity], '\n')
-
-          editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
-          editor.toggleLineCommentsInSelection()
+        it "outdents a single hard tab when indent is multiple hard tabs and and the session is using soft tabs", ->
+          editor.insertText('\t\t')
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(0)).toBe "\tvar quicksort = function () {"
+          editor.outdentSelectedRows()
           expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-          expect(buffer.lineForRow(1)).toBe ""
-          expect(buffer.lineForRow(2)).toBe "  var sort = function(items) {"
-
-        it "preserves selection emptiness", ->
-          editor.setCursorBufferPosition([4, 0])
-          editor.toggleLineCommentsInSelection()
-          expect(editor.getSelection().isEmpty()).toBeTruthy()
-
-        it "does not explode if the current language mode has no comment regex", ->
-          editor.destroy()
-          editor = atom.project.openSync(null, autoIndent: false)
-          editor.setSelectedBufferRange([[4, 5], [4, 5]])
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(4)).toBe "    while(items.length > 0) {"
-
-        it "uncomments when the line lacks the trailing whitespace in the comment regex", ->
-          editor.setCursorBufferPosition([10, 0])
-          editor.toggleLineCommentsInSelection()
-
-          expect(buffer.lineForRow(10)).toBe "// "
-          expect(editor.getSelectedBufferRange()).toEqual [[10, 3], [10, 3]]
-          editor.backspace()
-          expect(buffer.lineForRow(10)).toBe "//"
-
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(10)).toBe ""
-          expect(editor.getSelectedBufferRange()).toEqual [[10, 0], [10, 0]]
-
-        it "uncomments when the line has leading whitespace", ->
-          editor.setCursorBufferPosition([10, 0])
-          editor.toggleLineCommentsInSelection()
-
-          expect(buffer.lineForRow(10)).toBe "// "
-          editor.moveCursorToBeginningOfLine()
-          editor.insertText("  ")
-          editor.setSelectedBufferRange([[10, 0], [10, 0]])
-          editor.toggleLineCommentsInSelection()
-          expect(buffer.lineForRow(10)).toBe "  "
-
-      describe ".undo() and .redo()", ->
-        it "undoes/redoes the last change", ->
-          editor.insertText("foo")
-          editor.undo()
-          expect(buffer.lineForRow(0)).not.toContain "foo"
-
-          editor.redo()
-          expect(buffer.lineForRow(0)).toContain "foo"
-
-        it "batches the undo / redo of changes caused by multiple cursors", ->
-          editor.setCursorScreenPosition([0, 0])
-          editor.addCursorAtScreenPosition([1, 0])
-
-          editor.insertText("foo")
-          editor.backspace()
-
-          expect(buffer.lineForRow(0)).toContain "fovar"
-          expect(buffer.lineForRow(1)).toContain "fo "
-
-          editor.undo()
-
-          expect(buffer.lineForRow(0)).toContain "foo"
-          expect(buffer.lineForRow(1)).toContain "foo"
-
-          editor.redo()
-
-          expect(buffer.lineForRow(0)).not.toContain "foo"
-          expect(buffer.lineForRow(0)).toContain "fovar"
-
-        it "restores the selected ranges after undo and redo", ->
-          editor.setSelectedBufferRanges([[[1, 6], [1, 10]], [[1, 22], [1, 27]]])
-          editor.delete()
-          editor.delete()
-
-          selections = editor.getSelections()
-          expect(buffer.lineForRow(1)).toBe '  var = function( {'
-
-          expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 17], [1, 17]]]
-
-          editor.undo()
-          expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
-
-          editor.undo()
-          expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 10]], [[1, 22], [1, 27]]]
-
-          editor.redo()
-          expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
-
-        xit "restores folds after undo and redo", ->
-          editor.foldBufferRow(1)
-          editor.setSelectedBufferRange([[1, 0], [10, Infinity]], preserveFolds: true)
-          expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
-
-          editor.insertText """
-            \  // testing
-              function foo() {
-                return 1 + 2;
-              }
-          """
-          expect(editor.isFoldedAtBufferRow(1)).toBeFalsy()
-          editor.foldBufferRow(2)
-
-          editor.undo()
-          expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
-          expect(editor.isFoldedAtBufferRow(9)).toBeTruthy()
-          expect(editor.isFoldedAtBufferRow(10)).toBeFalsy()
-
-          editor.redo()
-          expect(editor.isFoldedAtBufferRow(1)).toBeFalsy()
-          expect(editor.isFoldedAtBufferRow(2)).toBeTruthy()
-
-      describe "begin/commitTransaction()", ->
-        it "restores the selection when the transaction is undone/redone", ->
-          buffer.setText('1234')
-          editor.setSelectedBufferRange([[0, 1], [0, 3]])
-          editor.beginTransaction()
-
-          editor.delete()
-          editor.moveCursorToEndOfLine()
-          editor.insertText('5')
-          expect(buffer.getText()).toBe '145'
-
-          editor.commitTransaction()
-
-          editor.undo()
-          expect(buffer.getText()).toBe '1234'
-          expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 3]]
-
-          editor.redo()
-          expect(buffer.getText()).toBe '145'
-          expect(editor.getSelectedBufferRange()).toEqual [[0, 3], [0, 3]]
-
-      describe "when the buffer is changed (via its direct api, rather than via than edit session)", ->
-        it "moves the cursor so it is in the same relative position of the buffer", ->
-          expect(editor.getCursorScreenPosition()).toEqual [0, 0]
-          editor.addCursorAtScreenPosition([0, 5])
-          editor.addCursorAtScreenPosition([1, 0])
-          [cursor1, cursor2, cursor3] = editor.getCursors()
-
-          buffer.insert([0, 1], 'abc')
-
-          expect(cursor1.getScreenPosition()).toEqual [0, 0]
-          expect(cursor2.getScreenPosition()).toEqual [0, 8]
-          expect(cursor3.getScreenPosition()).toEqual [1, 0]
-
-        it "does not destroy cursors or selections when a change encompasses them", ->
-          cursor = editor.getCursor()
-          cursor.setBufferPosition [3, 3]
-          editor.buffer.delete([[3, 1], [3, 5]])
-          expect(cursor.getBufferPosition()).toEqual [3, 1]
-          expect(editor.getCursors().indexOf(cursor)).not.toBe -1
-
-          selection = editor.getLastSelection()
-          selection.setBufferRange [[3, 5], [3, 10]]
-          editor.buffer.delete [[3, 3], [3, 8]]
-          expect(selection.getBufferRange()).toEqual [[3, 3], [3, 5]]
-          expect(editor.getSelections().indexOf(selection)).not.toBe -1
-
-        it "merges cursors when the change causes them to overlap", ->
-          editor.setCursorScreenPosition([0, 0])
-          editor.addCursorAtScreenPosition([0, 2])
-          editor.addCursorAtScreenPosition([1, 2])
-
-          [cursor1, cursor2, cursor3] = editor.getCursors()
-          expect(editor.getCursors().length).toBe 3
-
-          buffer.delete([[0, 0], [0, 2]])
-
-          expect(editor.getCursors().length).toBe 2
-          expect(editor.getCursors()).toEqual [cursor1, cursor3]
-          expect(cursor1.getBufferPosition()).toEqual [0,0]
-          expect(cursor3.getBufferPosition()).toEqual [1,2]
-
-    describe ".deleteLine()", ->
-      it "deletes the first line when the cursor is there", ->
-        editor.getCursor().moveToTop()
-        line1 = buffer.lineForRow(1)
-        count = buffer.getLineCount()
-        expect(buffer.lineForRow(0)).not.toBe(line1)
-        editor.deleteLine()
-        expect(buffer.lineForRow(0)).toBe(line1)
-        expect(buffer.getLineCount()).toBe(count - 1)
-
-      it "deletes the last line when the cursor is there", ->
-        count = buffer.getLineCount()
-        secondToLastLine = buffer.lineForRow(count - 2)
-        expect(buffer.lineForRow(count - 1)).not.toBe(secondToLastLine)
+
+        it "outdents when a mix of hard tabs and soft tabs are used", ->
+          editor.insertText('\t   ')
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(0)).toBe "   var quicksort = function () {"
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(0)).toBe " var quicksort = function () {"
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
+
+      describe "when one line is selected", ->
+        it "outdents line and retains editor", ->
+          editor.setSelectedBufferRange([[1,4], [1,14]])
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
+          expect(editor.getSelectedBufferRange()).toEqual [[1, 4 - editor.getTabLength()], [1, 14 - editor.getTabLength()]]
+
+      describe "when multiple lines are selected", ->
+        it "outdents selected lines and retains editor", ->
+          editor.setSelectedBufferRange([[0,1], [3,15]])
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
+          expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
+          expect(buffer.lineForRow(2)).toBe "  if (items.length <= 1) return items;"
+          expect(buffer.lineForRow(3)).toBe "  var pivot = items.shift(), current, left = [], right = [];"
+          expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [3, 15 - editor.getTabLength()]]
+
+        it "does not outdent the last line of the selection if it ends at column 0", ->
+          editor.setSelectedBufferRange([[0,1], [3,0]])
+          editor.outdentSelectedRows()
+          expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
+          expect(buffer.lineForRow(1)).toBe "var sort = function(items) {"
+          expect(buffer.lineForRow(2)).toBe "  if (items.length <= 1) return items;"
+          expect(buffer.lineForRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+
+          expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [3, 0]]
+
+    describe ".toggleLineCommentsInSelection()", ->
+      it "toggles comments on the selected lines", ->
+        editor.setSelectedBufferRange([[4, 5], [7, 5]])
+        editor.toggleLineCommentsInSelection()
+
+        expect(buffer.lineForRow(4)).toBe "    // while(items.length > 0) {"
+        expect(buffer.lineForRow(5)).toBe "    //   current = items.shift();"
+        expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
+        expect(buffer.lineForRow(7)).toBe "    // }"
+        expect(editor.getSelectedBufferRange()).toEqual [[4, 8], [7, 8]]
+
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(4)).toBe "    while(items.length > 0) {"
+        expect(buffer.lineForRow(5)).toBe "      current = items.shift();"
+        expect(buffer.lineForRow(6)).toBe "      current < pivot ? left.push(current) : right.push(current);"
+        expect(buffer.lineForRow(7)).toBe "    }"
+
+      it "does not comment the last line of a non-empty selection if it ends at column 0", ->
+        editor.setSelectedBufferRange([[4, 5], [7, 0]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(4)).toBe "    // while(items.length > 0) {"
+        expect(buffer.lineForRow(5)).toBe "    //   current = items.shift();"
+        expect(buffer.lineForRow(6)).toBe "    //   current < pivot ? left.push(current) : right.push(current);"
+        expect(buffer.lineForRow(7)).toBe "    }"
+
+      it "uncomments lines if all lines match the comment regex", ->
+        editor.setSelectedBufferRange([[0, 0], [0, 1]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
+
+        editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(0)).toBe "// // var quicksort = function () {"
+        expect(buffer.lineForRow(1)).toBe "//   var sort = function(items) {"
+        expect(buffer.lineForRow(2)).toBe "//     if (items.length <= 1) return items;"
+
+        editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
+        expect(buffer.lineForRow(1)).toBe "  var sort = function(items) {"
+        expect(buffer.lineForRow(2)).toBe "    if (items.length <= 1) return items;"
+
+        editor.setSelectedBufferRange([[0, 0], [0, Infinity]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
+
+      it "uncomments commented lines separated by an empty line", ->
+        editor.setSelectedBufferRange([[0, 0], [1, Infinity]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
+        expect(buffer.lineForRow(1)).toBe "//   var sort = function(items) {"
+
+        buffer.insert([0, Infinity], '\n')
+
+        editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
+        expect(buffer.lineForRow(1)).toBe ""
+        expect(buffer.lineForRow(2)).toBe "  var sort = function(items) {"
+
+      it "preserves selection emptiness", ->
+        editor.setCursorBufferPosition([4, 0])
+        editor.toggleLineCommentsInSelection()
+        expect(editor.getSelection().isEmpty()).toBeTruthy()
+
+      it "does not explode if the current language mode has no comment regex", ->
+        editor.destroy()
+        editor = atom.project.openSync(null, autoIndent: false)
+        editor.setSelectedBufferRange([[4, 5], [4, 5]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(4)).toBe "    while(items.length > 0) {"
+
+      it "uncomments when the line lacks the trailing whitespace in the comment regex", ->
+        editor.setCursorBufferPosition([10, 0])
+        editor.toggleLineCommentsInSelection()
+
+        expect(buffer.lineForRow(10)).toBe "// "
+        expect(editor.getSelectedBufferRange()).toEqual [[10, 3], [10, 3]]
+        editor.backspace()
+        expect(buffer.lineForRow(10)).toBe "//"
+
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(10)).toBe ""
+        expect(editor.getSelectedBufferRange()).toEqual [[10, 0], [10, 0]]
+
+      it "uncomments when the line has leading whitespace", ->
+        editor.setCursorBufferPosition([10, 0])
+        editor.toggleLineCommentsInSelection()
+
+        expect(buffer.lineForRow(10)).toBe "// "
+        editor.moveCursorToBeginningOfLine()
+        editor.insertText("  ")
+        editor.setSelectedBufferRange([[10, 0], [10, 0]])
+        editor.toggleLineCommentsInSelection()
+        expect(buffer.lineForRow(10)).toBe "  "
+
+    describe ".undo() and .redo()", ->
+      it "undoes/redoes the last change", ->
+        editor.insertText("foo")
+        editor.undo()
+        expect(buffer.lineForRow(0)).not.toContain "foo"
+
+        editor.redo()
+        expect(buffer.lineForRow(0)).toContain "foo"
+
+      it "batches the undo / redo of changes caused by multiple cursors", ->
+        editor.setCursorScreenPosition([0, 0])
+        editor.addCursorAtScreenPosition([1, 0])
+
+        editor.insertText("foo")
+        editor.backspace()
+
+        expect(buffer.lineForRow(0)).toContain "fovar"
+        expect(buffer.lineForRow(1)).toContain "fo "
+
+        editor.undo()
+
+        expect(buffer.lineForRow(0)).toContain "foo"
+        expect(buffer.lineForRow(1)).toContain "foo"
+
+        editor.redo()
+
+        expect(buffer.lineForRow(0)).not.toContain "foo"
+        expect(buffer.lineForRow(0)).toContain "fovar"
+
+      it "restores the selected ranges after undo and redo", ->
+        editor.setSelectedBufferRanges([[[1, 6], [1, 10]], [[1, 22], [1, 27]]])
+        editor.delete()
+        editor.delete()
+
+        selections = editor.getSelections()
+        expect(buffer.lineForRow(1)).toBe '  var = function( {'
+
+        expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 17], [1, 17]]]
+
+        editor.undo()
+        expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
+
+        editor.undo()
+        expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 10]], [[1, 22], [1, 27]]]
+
+        editor.redo()
+        expect(editor.getSelectedBufferRanges()).toEqual [[[1, 6], [1, 6]], [[1, 18], [1, 18]]]
+
+      xit "restores folds after undo and redo", ->
+        editor.foldBufferRow(1)
+        editor.setSelectedBufferRange([[1, 0], [10, Infinity]], preserveFolds: true)
+        expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
+
+        editor.insertText """
+          \  // testing
+            function foo() {
+              return 1 + 2;
+            }
+        """
+        expect(editor.isFoldedAtBufferRow(1)).toBeFalsy()
+        editor.foldBufferRow(2)
+
+        editor.undo()
+        expect(editor.isFoldedAtBufferRow(1)).toBeTruthy()
+        expect(editor.isFoldedAtBufferRow(9)).toBeTruthy()
+        expect(editor.isFoldedAtBufferRow(10)).toBeFalsy()
+
+        editor.redo()
+        expect(editor.isFoldedAtBufferRow(1)).toBeFalsy()
+        expect(editor.isFoldedAtBufferRow(2)).toBeTruthy()
+
+    describe "begin/commitTransaction()", ->
+      it "restores the selection when the transaction is undone/redone", ->
+        buffer.setText('1234')
+        editor.setSelectedBufferRange([[0, 1], [0, 3]])
+        editor.beginTransaction()
+
+        editor.delete()
+        editor.moveCursorToEndOfLine()
+        editor.insertText('5')
+        expect(buffer.getText()).toBe '145'
+
+        editor.commitTransaction()
+
+        editor.undo()
+        expect(buffer.getText()).toBe '1234'
+        expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 3]]
+
+        editor.redo()
+        expect(buffer.getText()).toBe '145'
+        expect(editor.getSelectedBufferRange()).toEqual [[0, 3], [0, 3]]
+
+    describe "when the buffer is changed (via its direct api, rather than via than edit session)", ->
+      it "moves the cursor so it is in the same relative position of the buffer", ->
+        expect(editor.getCursorScreenPosition()).toEqual [0, 0]
+        editor.addCursorAtScreenPosition([0, 5])
+        editor.addCursorAtScreenPosition([1, 0])
+        [cursor1, cursor2, cursor3] = editor.getCursors()
+
+        buffer.insert([0, 1], 'abc')
+
+        expect(cursor1.getScreenPosition()).toEqual [0, 0]
+        expect(cursor2.getScreenPosition()).toEqual [0, 8]
+        expect(cursor3.getScreenPosition()).toEqual [1, 0]
+
+      it "does not destroy cursors or selections when a change encompasses them", ->
+        cursor = editor.getCursor()
+        cursor.setBufferPosition [3, 3]
+        editor.buffer.delete([[3, 1], [3, 5]])
+        expect(cursor.getBufferPosition()).toEqual [3, 1]
+        expect(editor.getCursors().indexOf(cursor)).not.toBe -1
+
+        selection = editor.getLastSelection()
+        selection.setBufferRange [[3, 5], [3, 10]]
+        editor.buffer.delete [[3, 3], [3, 8]]
+        expect(selection.getBufferRange()).toEqual [[3, 3], [3, 5]]
+        expect(editor.getSelections().indexOf(selection)).not.toBe -1
+
+      it "merges cursors when the change causes them to overlap", ->
+        editor.setCursorScreenPosition([0, 0])
+        editor.addCursorAtScreenPosition([0, 2])
+        editor.addCursorAtScreenPosition([1, 2])
+
+        [cursor1, cursor2, cursor3] = editor.getCursors()
+        expect(editor.getCursors().length).toBe 3
+
+        buffer.delete([[0, 0], [0, 2]])
+
+        expect(editor.getCursors().length).toBe 2
+        expect(editor.getCursors()).toEqual [cursor1, cursor3]
+        expect(cursor1.getBufferPosition()).toEqual [0,0]
+        expect(cursor3.getBufferPosition()).toEqual [1,2]
+
+  describe ".deleteLine()", ->
+    it "deletes the first line when the cursor is there", ->
+      editor.getCursor().moveToTop()
+      line1 = buffer.lineForRow(1)
+      count = buffer.getLineCount()
+      expect(buffer.lineForRow(0)).not.toBe(line1)
+      editor.deleteLine()
+      expect(buffer.lineForRow(0)).toBe(line1)
+      expect(buffer.getLineCount()).toBe(count - 1)
+
+    it "deletes the last line when the cursor is there", ->
+      count = buffer.getLineCount()
+      secondToLastLine = buffer.lineForRow(count - 2)
+      expect(buffer.lineForRow(count - 1)).not.toBe(secondToLastLine)
+      editor.getCursor().moveToBottom()
+      editor.deleteLine()
+      newCount = buffer.getLineCount()
+      expect(buffer.lineForRow(newCount - 1)).toBe(secondToLastLine)
+      expect(newCount).toBe(count - 1)
+
+    it "deletes whole lines when partial lines are selected", ->
+      editor.setSelectedBufferRange([[0, 2], [1, 2]])
+      line2 = buffer.lineForRow(2)
+      count = buffer.getLineCount()
+      expect(buffer.lineForRow(0)).not.toBe(line2)
+      expect(buffer.lineForRow(1)).not.toBe(line2)
+      editor.deleteLine()
+      expect(buffer.lineForRow(0)).toBe(line2)
+      expect(buffer.getLineCount()).toBe(count - 2)
+
+    it "only deletes first line if only newline is selected on second line", ->
+      editor.setSelectedBufferRange([[0, 2], [1, 0]])
+      line1 = buffer.lineForRow(1)
+      count = buffer.getLineCount()
+      expect(buffer.lineForRow(0)).not.toBe(line1)
+      editor.deleteLine()
+      expect(buffer.lineForRow(0)).toBe(line1)
+      expect(buffer.getLineCount()).toBe(count - 1)
+
+    it "deletes the entire region when invoke on a folded region", ->
+      editor.foldBufferRow(1)
+      editor.getCursor().moveToTop()
+      editor.getCursor().moveDown()
+      expect(buffer.getLineCount()).toBe(13)
+      editor.deleteLine()
+      expect(buffer.getLineCount()).toBe(4)
+
+    it "deletes the entire file from the bottom up", ->
+      count = buffer.getLineCount()
+      expect(count).toBeGreaterThan(0)
+      for line in [0...count]
         editor.getCursor().moveToBottom()
         editor.deleteLine()
-        newCount = buffer.getLineCount()
-        expect(buffer.lineForRow(newCount - 1)).toBe(secondToLastLine)
-        expect(newCount).toBe(count - 1)
-
-      it "deletes whole lines when partial lines are selected", ->
-        editor.setSelectedBufferRange([[0, 2], [1, 2]])
-        line2 = buffer.lineForRow(2)
+      expect(buffer.getLineCount()).toBe(1)
+      expect(buffer.getText()).toBe('')
+
+    it "deletes the entire file from the top down", ->
+      count = buffer.getLineCount()
+      expect(count).toBeGreaterThan(0)
+      for line in [0...count]
+        editor.getCursor().moveToTop()
+        editor.deleteLine()
+      expect(buffer.getLineCount()).toBe(1)
+      expect(buffer.getText()).toBe('')
+
+    describe "when soft wrap is enabled", ->
+      it "deletes the entire line that the cursor is on", ->
+        editor.setSoftWrap(true)
+        editor.setEditorWidthInChars(10)
+        editor.setCursorBufferPosition([6])
+
+        line7 = buffer.lineForRow(7)
         count = buffer.getLineCount()
-        expect(buffer.lineForRow(0)).not.toBe(line2)
-        expect(buffer.lineForRow(1)).not.toBe(line2)
+        expect(buffer.lineForRow(6)).not.toBe(line7)
         editor.deleteLine()
-        expect(buffer.lineForRow(0)).toBe(line2)
-        expect(buffer.getLineCount()).toBe(count - 2)
-
-      it "only deletes first line if only newline is selected on second line", ->
-        editor.setSelectedBufferRange([[0, 2], [1, 0]])
-        line1 = buffer.lineForRow(1)
-        count = buffer.getLineCount()
-        expect(buffer.lineForRow(0)).not.toBe(line1)
+        expect(buffer.lineForRow(6)).toBe(line7)
+        expect(buffer.getLineCount()).toBe(count - 1)
+
+    describe "when the line being deleted preceeds a fold, and the command is undone", ->
+      it "restores the line and preserves the fold", ->
+        editor.setCursorBufferPosition([4])
+        editor.foldCurrentRow()
+        expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
+        editor.setCursorBufferPosition([3])
         editor.deleteLine()
-        expect(buffer.lineForRow(0)).toBe(line1)
-        expect(buffer.getLineCount()).toBe(count - 1)
-
-      it "deletes the entire region when invoke on a folded region", ->
-        editor.foldBufferRow(1)
-        editor.getCursor().moveToTop()
-        editor.getCursor().moveDown()
-        expect(buffer.getLineCount()).toBe(13)
-        editor.deleteLine()
-        expect(buffer.getLineCount()).toBe(4)
-
-      it "deletes the entire file from the bottom up", ->
-        count = buffer.getLineCount()
-        expect(count).toBeGreaterThan(0)
-        for line in [0...count]
-          editor.getCursor().moveToBottom()
-          editor.deleteLine()
-        expect(buffer.getLineCount()).toBe(1)
-        expect(buffer.getText()).toBe('')
-
-      it "deletes the entire file from the top down", ->
-        count = buffer.getLineCount()
-        expect(count).toBeGreaterThan(0)
-        for line in [0...count]
-          editor.getCursor().moveToTop()
-          editor.deleteLine()
-        expect(buffer.getLineCount()).toBe(1)
-        expect(buffer.getText()).toBe('')
-
-      describe "when soft wrap is enabled", ->
-        it "deletes the entire line that the cursor is on", ->
-          editor.setSoftWrap(true)
-          editor.setEditorWidthInChars(10)
-          editor.setCursorBufferPosition([6])
-
-          line7 = buffer.lineForRow(7)
-          count = buffer.getLineCount()
-          expect(buffer.lineForRow(6)).not.toBe(line7)
-          editor.deleteLine()
-          expect(buffer.lineForRow(6)).toBe(line7)
-          expect(buffer.getLineCount()).toBe(count - 1)
-
-      describe "when the line being deleted preceeds a fold, and the command is undone", ->
-        it "restores the line and preserves the fold", ->
-          editor.setCursorBufferPosition([4])
-          editor.foldCurrentRow()
-          expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
-          editor.setCursorBufferPosition([3])
-          editor.deleteLine()
-          expect(editor.isFoldedAtScreenRow(3)).toBeTruthy()
-          expect(buffer.lineForRow(3)).toBe '    while(items.length > 0) {'
-          editor.undo()
-          expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
-          expect(buffer.lineForRow(3)).toBe '    var pivot = items.shift(), current, left = [], right = [];'
-
-    describe ".replaceSelectedText(options, fn)", ->
-      describe "when no text is selected", ->
-        it "inserts the text returned from the function at the cursor position", ->
-          editor.replaceSelectedText {}, -> '123'
-          expect(buffer.lineForRow(0)).toBe '123var quicksort = function () {'
-
-          editor.replaceSelectedText {selectWordIfEmpty: true}, -> 'var'
-          editor.setCursorBufferPosition([0])
-          expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
-
-          editor.setCursorBufferPosition([10])
-          editor.replaceSelectedText null, -> ''
-          expect(buffer.lineForRow(10)).toBe ''
-
-      describe "when text is selected", ->
-        it "replaces the selected text with the text returned from the function", ->
+        expect(editor.isFoldedAtScreenRow(3)).toBeTruthy()
+        expect(buffer.lineForRow(3)).toBe '    while(items.length > 0) {'
+        editor.undo()
+        expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
+        expect(buffer.lineForRow(3)).toBe '    var pivot = items.shift(), current, left = [], right = [];'
+
+  describe ".replaceSelectedText(options, fn)", ->
+    describe "when no text is selected", ->
+      it "inserts the text returned from the function at the cursor position", ->
+        editor.replaceSelectedText {}, -> '123'
+        expect(buffer.lineForRow(0)).toBe '123var quicksort = function () {'
+
+        editor.replaceSelectedText {selectWordIfEmpty: true}, -> 'var'
+        editor.setCursorBufferPosition([0])
+        expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
+
+        editor.setCursorBufferPosition([10])
+        editor.replaceSelectedText null, -> ''
+        expect(buffer.lineForRow(10)).toBe ''
+
+    describe "when text is selected", ->
+      it "replaces the selected text with the text returned from the function", ->
+        editor.setSelectedBufferRange([[0, 1], [0, 3]])
+        editor.replaceSelectedText {}, -> 'ia'
+        expect(buffer.lineForRow(0)).toBe 'via quicksort = function () {'
+
+  describe ".transpose()", ->
+    it "swaps two characters", ->
+      editor.buffer.setText("abc")
+      editor.setCursorScreenPosition([0, 1])
+      editor.transpose()
+      expect(editor.lineForBufferRow(0)).toBe 'bac'
+
+    it "reverses a selection", ->
+      editor.buffer.setText("xabcz")
+      editor.setSelectedBufferRange([[0, 1], [0, 4]])
+      editor.transpose()
+      expect(editor.lineForBufferRow(0)).toBe 'xcbaz'
+
+  describe ".upperCase()", ->
+    describe "when there is no selection", ->
+      it "upper cases the current word", ->
+        editor.buffer.setText("aBc")
+        editor.setCursorScreenPosition([0, 1])
+        editor.upperCase()
+        expect(editor.lineForBufferRow(0)).toBe 'ABC'
+        expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]]
+
+    describe "when there is a selection", ->
+      it "upper cases the current selection", ->
+        editor.buffer.setText("abc")
+        editor.setSelectedBufferRange([[0,0], [0,2]])
+        editor.upperCase()
+        expect(editor.lineForBufferRow(0)).toBe 'ABc'
+        expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 2]]
+
+  describe ".lowerCase()", ->
+    describe "when there is no selection", ->
+      it "lower cases the current word", ->
+        editor.buffer.setText("aBC")
+        editor.setCursorScreenPosition([0, 1])
+        editor.lowerCase()
+        expect(editor.lineForBufferRow(0)).toBe 'abc'
+        expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]]
+
+    describe "when there is a selection", ->
+      it "lower cases the current selection", ->
+        editor.buffer.setText("ABC")
+        editor.setSelectedBufferRange([[0,0], [0,2]])
+        editor.lowerCase()
+        expect(editor.lineForBufferRow(0)).toBe 'abC'
+        expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 2]]
+
+  describe "soft-tabs detection", ->
+    it "assigns soft / hard tabs based on the contents of the buffer, or uses the default if unknown", ->
+      editor = atom.project.openSync('sample.js', softTabs: false)
+      expect(editor.getSoftTabs()).toBeTruthy()
+
+      editor = atom.project.openSync('sample-with-tabs.coffee', softTabs: true)
+      expect(editor.getSoftTabs()).toBeFalsy()
+
+      editor = atom.project.openSync(null, softTabs: false)
+      expect(editor.getSoftTabs()).toBeFalsy()
+
+  describe ".indentLevelForLine(line)", ->
+    it "returns the indent level when the line has only leading whitespace", ->
+      expect(editor.indentLevelForLine("    hello")).toBe(2)
+      expect(editor.indentLevelForLine("   hello")).toBe(1.5)
+
+    it "returns the indent level when the line has only leading tabs", ->
+      expect(editor.indentLevelForLine("\t\thello")).toBe(2)
+
+    it "returns the indent level when the line has mixed leading whitespace and tabs", ->
+      expect(editor.indentLevelForLine("\t  hello")).toBe(2)
+      expect(editor.indentLevelForLine("  \thello")).toBe(2)
+      expect(editor.indentLevelForLine("  \t hello")).toBe(2.5)
+      expect(editor.indentLevelForLine("  \t \thello")).toBe(3.5)
+
+  describe "when the buffer is reloaded", ->
+    it "preserves the current cursor position", ->
+      editor.setCursorScreenPosition([0, 1])
+      editor.buffer.reload()
+      expect(editor.getCursorScreenPosition()).toEqual [0,1]
+
+  describe "when a better-matched grammar is added to syntax", ->
+    it "switches to the better-matched grammar and re-tokenizes the buffer", ->
+      jsGrammar = atom.syntax.selectGrammar('a.js')
+      atom.syntax.removeGrammar(jsGrammar)
+
+      editor2 = atom.project.openSync('sample.js', autoIndent: false)
+      expect(editor2.getGrammar()).toBe atom.syntax.nullGrammar
+      expect(editor2.lineForScreenRow(0).tokens.length).toBe 1
+
+      atom.syntax.addGrammar(jsGrammar)
+      expect(editor2.getGrammar()).toBe jsGrammar
+      expect(editor2.lineForScreenRow(0).tokens.length).toBeGreaterThan 1
+
+  describe "auto-indent", ->
+    copyText = (text, {startColumn}={}) ->
+      startColumn ?= 0
+      editor.setCursorBufferPosition([0, 0])
+      editor.insertText(text)
+      numberOfNewlines = text.match(/\n/g)?.length
+      endColumn = text.match(/[^\n]*$/)[0]?.length
+      editor.getSelection().setBufferRange([[0,startColumn], [numberOfNewlines,endColumn]])
+      editor.cutSelectedText()
+
+    describe "editor.autoIndent", ->
+      describe "when editor.autoIndent is false (default)", ->
+        describe "when `indent` is triggered", ->
+          it "does not auto-indent the line", ->
+            editor.setCursorBufferPosition([1, 30])
+            editor.insertText("\n ")
+            expect(editor.lineForBufferRow(2)).toBe " "
+
+            atom.config.set("editor.autoIndent", false)
+            editor.indent()
+            expect(editor.lineForBufferRow(2)).toBe "   "
+
+      describe "when editor.autoIndent is true", ->
+        beforeEach ->
+          atom.config.set("editor.autoIndent", true)
+
+        describe "when `indent` is triggered", ->
+          it "auto-indents the line", ->
+            editor.setCursorBufferPosition([1, 30])
+            editor.insertText("\n ")
+            expect(editor.lineForBufferRow(2)).toBe " "
+
+            atom.config.set("editor.autoIndent", true)
+            editor.indent()
+            expect(editor.lineForBufferRow(2)).toBe "    "
+
+        describe "when a newline is added", ->
+          describe "when the line preceding the newline adds a new level of indentation", ->
+            it "indents the newline to one additional level of indentation beyond the preceding line", ->
+              editor.setCursorBufferPosition([1, Infinity])
+              editor.insertText('\n')
+              expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
+
+          describe "when the line preceding the newline does't add a level of indentation", ->
+            it "indents the new line to the same level a as the preceding line", ->
+              editor.setCursorBufferPosition([5, 13])
+              editor.insertText('\n')
+              expect(editor.indentationForBufferRow(6)).toBe editor.indentationForBufferRow(5)
+
+          describe "when the line preceding the newline is a comment", ->
+            it "maintains the indent of the commented line", ->
+              editor.setCursorBufferPosition([0, 0])
+              editor.insertText('    //')
+              editor.setCursorBufferPosition([0, Infinity])
+              editor.insertText('\n')
+              expect(editor.indentationForBufferRow(1)).toBe 2
+
+          it "does not indent the line preceding the newline", ->
+            editor.setCursorBufferPosition([2, 0])
+            editor.insertText('  var this-line-should-be-indented-more\n')
+            expect(editor.indentationForBufferRow(1)).toBe 1
+
+            atom.config.set("editor.autoIndent", true)
+            editor.setCursorBufferPosition([2, Infinity])
+            editor.insertText('\n')
+            expect(editor.indentationForBufferRow(1)).toBe 1
+            expect(editor.indentationForBufferRow(2)).toBe 1
+
+        describe "when inserted text matches a decrease indent pattern", ->
+          describe "when the preceding line matches an increase indent pattern", ->
+            it "decreases the indentation to match that of the preceding line", ->
+              editor.setCursorBufferPosition([1, Infinity])
+              editor.insertText('\n')
+              expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
+              editor.insertText('}')
+              expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1)
+
+          describe "when the preceding line doesn't match an increase indent pattern", ->
+            it "decreases the indentation to be one level below that of the preceding line", ->
+              editor.setCursorBufferPosition([3, Infinity])
+              editor.insertText('\n    ')
+              expect(editor.indentationForBufferRow(4)).toBe editor.indentationForBufferRow(3)
+              editor.insertText('}')
+              expect(editor.indentationForBufferRow(4)).toBe editor.indentationForBufferRow(3) - 1
+
+            it "doesn't break when decreasing the indentation on a row that has no indentation", ->
+              editor.setCursorBufferPosition([12, Infinity])
+              editor.insertText("\n}; # too many closing brackets!")
+              expect(editor.lineForBufferRow(13)).toBe "}; # too many closing brackets!"
+
+        describe "when inserted text does not match a decrease indent pattern", ->
+          it "does not the indentation", ->
+            editor.setCursorBufferPosition([12, 0])
+            editor.insertText('  ')
+            expect(editor.lineForBufferRow(12)).toBe '  };'
+            editor.insertText('\t\t')
+            expect(editor.lineForBufferRow(12)).toBe '  \t\t};'
+
+        describe "when the current line does not match a decrease indent pattern", ->
+          it "leaves the line unchanged", ->
+            editor.setCursorBufferPosition([2, 4])
+            expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
+            editor.insertText('foo')
+            expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
+
+    describe "editor.normalizeIndentOnPaste", ->
+      beforeEach ->
+        atom.config.set('editor.normalizeIndentOnPaste', true)
+
+      it "does not normalize the indentation level of the text when editor.normalizeIndentOnPaste is false", ->
+        copyText("   function() {\nvar cool = 1;\n  }\n")
+        atom.config.set('editor.normalizeIndentOnPaste', false)
+        editor.setCursorBufferPosition([5, 2])
+        editor.pasteText()
+        expect(editor.lineForBufferRow(5)).toBe "     function() {"
+        expect(editor.lineForBufferRow(6)).toBe "var cool = 1;"
+        expect(editor.lineForBufferRow(7)).toBe "  }"
+
+      describe "when the inserted text contains no newlines", ->
+        it "does not adjust the indentation level of the text", ->
+          editor.setCursorBufferPosition([5, 2])
+          editor.insertText("foo", indentBasis: 5)
+          expect(editor.lineForBufferRow(5)).toBe "  foo    current = items.shift();"
+
+        it "does not adjust the whitespace if there are preceding characters", ->
+          copyText(" foo")
+          editor.setCursorBufferPosition([5, 30])
+          editor.pasteText()
+
+          expect(editor.lineForBufferRow(5)).toBe "      current = items.shift(); foo"
+
+      describe "when the inserted text contains newlines", ->
+        describe "when the cursor is preceded only by whitespace characters", ->
+          it "normalizes indented lines to the cursor's current indentation level", ->
+            copyText("    while (true) {\n      foo();\n    }\n", {startColumn: 2})
+            editor.setCursorBufferPosition([3, 4])
+            editor.pasteText()
+
+            expect(editor.lineForBufferRow(3)).toBe "    while (true) {"
+            expect(editor.lineForBufferRow(4)).toBe "      foo();"
+            expect(editor.lineForBufferRow(5)).toBe "    }"
+            expect(editor.lineForBufferRow(6)).toBe "var pivot = items.shift(), current, left = [], right = [];"
+
+        describe "when the cursor is preceded by non-whitespace characters", ->
+          it "normalizes the indentation level of all lines based on the level of the existing first line", ->
+            copyText("    while (true) {\n      foo();\n    }\n", {startColumn: 0})
+            editor.setCursorBufferPosition([1, Infinity])
+            editor.pasteText()
+
+            expect(editor.lineForBufferRow(1)).toBe "  var sort = function(items) {while (true) {"
+            expect(editor.lineForBufferRow(2)).toBe "    foo();"
+            expect(editor.lineForBufferRow(3)).toBe "  }"
+            expect(editor.lineForBufferRow(4)).toBe ""
+
+    it "autoIndentSelectedRows auto-indents the selection", ->
+      editor.setCursorBufferPosition([2, 0])
+      editor.insertText("function() {\ninside=true\n}\n  i=1\n")
+      editor.getSelection().setBufferRange([[2,0], [6,0]])
+      editor.autoIndentSelectedRows()
+
+      expect(editor.lineForBufferRow(2)).toBe "    function() {"
+      expect(editor.lineForBufferRow(3)).toBe "      inside=true"
+      expect(editor.lineForBufferRow(4)).toBe "    }"
+      expect(editor.lineForBufferRow(5)).toBe "    i=1"
+
+  describe ".destroy()", ->
+    it "destroys all markers associated with the edit session", ->
+      expect(buffer.getMarkerCount()).toBeGreaterThan 0
+      editor.destroy()
+      expect(buffer.getMarkerCount()).toBe 0
+
+  describe ".joinLines()", ->
+    describe "when no text is selected", ->
+      describe "when the line below isn't empty", ->
+        it "joins the line below with the current line separated by a space and moves the cursor to the start of line that was moved up", ->
+          editor.joinLines()
+          expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () { var sort = function(items) {'
+          expect(editor.getCursorBufferPosition()).toEqual [0, 30]
+
+      describe "when the line below is empty", ->
+        it "deletes the line below and moves the cursor to the end of the line", ->
+          editor.setCursorBufferPosition([9])
+          editor.joinLines()
+          expect(editor.lineForBufferRow(9)).toBe '  };'
+          expect(editor.lineForBufferRow(10)).toBe '  return sort(Array.apply(this, arguments));'
+          expect(editor.getCursorBufferPosition()).toEqual [9, 4]
+
+      describe "when the cursor is on the last row", ->
+        it "does nothing", ->
+          editor.setCursorBufferPosition([Infinity, Infinity])
+          editor.joinLines()
+          expect(editor.lineForBufferRow(12)).toBe '};'
+
+    describe "when text is selected", ->
+      describe "when the selection does not span multiple lines", ->
+        it "joins the line below with the current line separated by a space and retains the selected text", ->
           editor.setSelectedBufferRange([[0, 1], [0, 3]])
-          editor.replaceSelectedText {}, -> 'ia'
-          expect(buffer.lineForRow(0)).toBe 'via quicksort = function () {'
-
-    describe ".transpose()", ->
-      it "swaps two characters", ->
-        editor.buffer.setText("abc")
-        editor.setCursorScreenPosition([0, 1])
-        editor.transpose()
-        expect(editor.lineForBufferRow(0)).toBe 'bac'
-
-      it "reverses a selection", ->
-        editor.buffer.setText("xabcz")
-        editor.setSelectedBufferRange([[0, 1], [0, 4]])
-        editor.transpose()
-        expect(editor.lineForBufferRow(0)).toBe 'xcbaz'
-
-    describe ".upperCase()", ->
-      describe "when there is no selection", ->
-        it "upper cases the current word", ->
-          editor.buffer.setText("aBc")
-          editor.setCursorScreenPosition([0, 1])
-          editor.upperCase()
-          expect(editor.lineForBufferRow(0)).toBe 'ABC'
-          expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]]
-
-      describe "when there is a selection", ->
-        it "upper cases the current selection", ->
-          editor.buffer.setText("abc")
-          editor.setSelectedBufferRange([[0,0], [0,2]])
-          editor.upperCase()
-          expect(editor.lineForBufferRow(0)).toBe 'ABc'
-          expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 2]]
-
-    describe ".lowerCase()", ->
-      describe "when there is no selection", ->
-        it "lower cases the current word", ->
-          editor.buffer.setText("aBC")
-          editor.setCursorScreenPosition([0, 1])
-          editor.lowerCase()
-          expect(editor.lineForBufferRow(0)).toBe 'abc'
-          expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 1]]
-
-      describe "when there is a selection", ->
-        it "lower cases the current selection", ->
-          editor.buffer.setText("ABC")
-          editor.setSelectedBufferRange([[0,0], [0,2]])
-          editor.lowerCase()
-          expect(editor.lineForBufferRow(0)).toBe 'abC'
-          expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [0, 2]]
-
-    describe "soft-tabs detection", ->
-      it "assigns soft / hard tabs based on the contents of the buffer, or uses the default if unknown", ->
-        editor = atom.project.openSync('sample.js', softTabs: false)
-        expect(editor.getSoftTabs()).toBeTruthy()
-
-        editor = atom.project.openSync('sample-with-tabs.coffee', softTabs: true)
-        expect(editor.getSoftTabs()).toBeFalsy()
-
-        editor = atom.project.openSync(null, softTabs: false)
-        expect(editor.getSoftTabs()).toBeFalsy()
-
-    describe ".indentLevelForLine(line)", ->
-      it "returns the indent level when the line has only leading whitespace", ->
-        expect(editor.indentLevelForLine("    hello")).toBe(2)
-        expect(editor.indentLevelForLine("   hello")).toBe(1.5)
-
-      it "returns the indent level when the line has only leading tabs", ->
-        expect(editor.indentLevelForLine("\t\thello")).toBe(2)
-
-      it "returns the indent level when the line has mixed leading whitespace and tabs", ->
-        expect(editor.indentLevelForLine("\t  hello")).toBe(2)
-        expect(editor.indentLevelForLine("  \thello")).toBe(2)
-        expect(editor.indentLevelForLine("  \t hello")).toBe(2.5)
-        expect(editor.indentLevelForLine("  \t \thello")).toBe(3.5)
-
-    describe "when the buffer is reloaded", ->
-      it "preserves the current cursor position", ->
-        editor.setCursorScreenPosition([0, 1])
-        editor.buffer.reload()
-        expect(editor.getCursorScreenPosition()).toEqual [0,1]
-
-    describe "when a better-matched grammar is added to syntax", ->
-      it "switches to the better-matched grammar and re-tokenizes the buffer", ->
-        jsGrammar = atom.syntax.selectGrammar('a.js')
-        atom.syntax.removeGrammar(jsGrammar)
-
-        editor2 = atom.project.openSync('sample.js', autoIndent: false)
-        expect(editor2.getGrammar()).toBe atom.syntax.nullGrammar
-        expect(editor2.lineForScreenRow(0).tokens.length).toBe 1
-
-        atom.syntax.addGrammar(jsGrammar)
-        expect(editor2.getGrammar()).toBe jsGrammar
-        expect(editor2.lineForScreenRow(0).tokens.length).toBeGreaterThan 1
-
-    describe "auto-indent", ->
-      copyText = (text, {startColumn}={}) ->
-        startColumn ?= 0
+          editor.joinLines()
+          expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () { var sort = function(items) {'
+          expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 3]]
+
+      describe "when the selection spans multiple lines", ->
+        it "joins all selected lines separated by a space and retains the selected text", ->
+          editor.setSelectedBufferRange([[9, 3], [12, 1]])
+          editor.joinLines()
+          expect(editor.lineForBufferRow(9)).toBe '  }; return sort(Array.apply(this, arguments)); };'
+          expect(editor.getSelectedBufferRange()).toEqual [[9, 3], [9, 49]]
+
+  describe ".shouldPromptToSave()", ->
+    it "returns false when an edit session's buffer is in use by more than one session", ->
+      jasmine.unspy(editor, 'shouldPromptToSave')
+      expect(editor.shouldPromptToSave()).toBeFalsy()
+      buffer.setText('changed')
+      expect(editor.shouldPromptToSave()).toBeTruthy()
+      editor2 = atom.project.openSync('sample.js', autoIndent: false)
+      expect(editor.shouldPromptToSave()).toBeFalsy()
+      editor2.destroy()
+      expect(editor.shouldPromptToSave()).toBeTruthy()
+
+  describe "when the edit session contains surrogate pair characters", ->
+    it "correctly backspaces over them", ->
+      editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97')
+      editor.moveCursorToBottom()
+      editor.backspace()
+      expect(editor.getText()).toBe '\uD835\uDF97\uD835\uDF97'
+      editor.backspace()
+      expect(editor.getText()).toBe '\uD835\uDF97'
+      editor.backspace()
+      expect(editor.getText()).toBe ''
+
+    it "correctly deletes over them", ->
+      editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97')
+      editor.moveCursorToTop()
+      editor.delete()
+      expect(editor.getText()).toBe '\uD835\uDF97\uD835\uDF97'
+      editor.delete()
+      expect(editor.getText()).toBe '\uD835\uDF97'
+      editor.delete()
+      expect(editor.getText()).toBe ''
+
+    it "correctly moves over them", ->
+      editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97\n')
+      editor.moveCursorToTop()
+      editor.moveCursorRight()
+      expect(editor.getCursorBufferPosition()).toEqual [0, 2]
+      editor.moveCursorRight()
+      expect(editor.getCursorBufferPosition()).toEqual [0, 4]
+      editor.moveCursorRight()
+      expect(editor.getCursorBufferPosition()).toEqual [0, 6]
+      editor.moveCursorRight()
+      expect(editor.getCursorBufferPosition()).toEqual [1, 0]
+      editor.moveCursorLeft()
+      expect(editor.getCursorBufferPosition()).toEqual [0, 6]
+      editor.moveCursorLeft()
+      expect(editor.getCursorBufferPosition()).toEqual [0, 4]
+      editor.moveCursorLeft()
+      expect(editor.getCursorBufferPosition()).toEqual [0, 2]
+      editor.moveCursorLeft()
+      expect(editor.getCursorBufferPosition()).toEqual [0, 0]
+
+  describe ".setIndentationForBufferRow", ->
+    describe "when the editor uses soft tabs but the row has hard tabs", ->
+      it "only replaces whitespace charachters", ->
+        editor.setSoftWrap(true)
+        editor.setText("	1\n	2")
         editor.setCursorBufferPosition([0, 0])
-        editor.insertText(text)
-        numberOfNewlines = text.match(/\n/g)?.length
-        endColumn = text.match(/[^\n]*$/)[0]?.length
-        editor.getSelection().setBufferRange([[0,startColumn], [numberOfNewlines,endColumn]])
-        editor.cutSelectedText()
-
-      describe "editor.autoIndent", ->
-        describe "when editor.autoIndent is false (default)", ->
-          describe "when `indent` is triggered", ->
-            it "does not auto-indent the line", ->
-              editor.setCursorBufferPosition([1, 30])
-              editor.insertText("\n ")
-              expect(editor.lineForBufferRow(2)).toBe " "
-
-              atom.config.set("editor.autoIndent", false)
-              editor.indent()
-              expect(editor.lineForBufferRow(2)).toBe "   "
-
-        describe "when editor.autoIndent is true", ->
-          beforeEach ->
-            atom.config.set("editor.autoIndent", true)
-
-          describe "when `indent` is triggered", ->
-            it "auto-indents the line", ->
-              editor.setCursorBufferPosition([1, 30])
-              editor.insertText("\n ")
-              expect(editor.lineForBufferRow(2)).toBe " "
-
-              atom.config.set("editor.autoIndent", true)
-              editor.indent()
-              expect(editor.lineForBufferRow(2)).toBe "    "
-
-          describe "when a newline is added", ->
-            describe "when the line preceding the newline adds a new level of indentation", ->
-              it "indents the newline to one additional level of indentation beyond the preceding line", ->
-                editor.setCursorBufferPosition([1, Infinity])
-                editor.insertText('\n')
-                expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
-
-            describe "when the line preceding the newline does't add a level of indentation", ->
-              it "indents the new line to the same level a as the preceding line", ->
-                editor.setCursorBufferPosition([5, 13])
-                editor.insertText('\n')
-                expect(editor.indentationForBufferRow(6)).toBe editor.indentationForBufferRow(5)
-
-            describe "when the line preceding the newline is a comment", ->
-              it "maintains the indent of the commented line", ->
-                editor.setCursorBufferPosition([0, 0])
-                editor.insertText('    //')
-                editor.setCursorBufferPosition([0, Infinity])
-                editor.insertText('\n')
-                expect(editor.indentationForBufferRow(1)).toBe 2
-
-            it "does not indent the line preceding the newline", ->
-              editor.setCursorBufferPosition([2, 0])
-              editor.insertText('  var this-line-should-be-indented-more\n')
-              expect(editor.indentationForBufferRow(1)).toBe 1
-
-              atom.config.set("editor.autoIndent", true)
-              editor.setCursorBufferPosition([2, Infinity])
-              editor.insertText('\n')
-              expect(editor.indentationForBufferRow(1)).toBe 1
-              expect(editor.indentationForBufferRow(2)).toBe 1
-
-          describe "when inserted text matches a decrease indent pattern", ->
-            describe "when the preceding line matches an increase indent pattern", ->
-              it "decreases the indentation to match that of the preceding line", ->
-                editor.setCursorBufferPosition([1, Infinity])
-                editor.insertText('\n')
-                expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
-                editor.insertText('}')
-                expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1)
-
-            describe "when the preceding line doesn't match an increase indent pattern", ->
-              it "decreases the indentation to be one level below that of the preceding line", ->
-                editor.setCursorBufferPosition([3, Infinity])
-                editor.insertText('\n    ')
-                expect(editor.indentationForBufferRow(4)).toBe editor.indentationForBufferRow(3)
-                editor.insertText('}')
-                expect(editor.indentationForBufferRow(4)).toBe editor.indentationForBufferRow(3) - 1
-
-              it "doesn't break when decreasing the indentation on a row that has no indentation", ->
-                editor.setCursorBufferPosition([12, Infinity])
-                editor.insertText("\n}; # too many closing brackets!")
-                expect(editor.lineForBufferRow(13)).toBe "}; # too many closing brackets!"
-
-          describe "when inserted text does not match a decrease indent pattern", ->
-            it "does not the indentation", ->
-              editor.setCursorBufferPosition([12, 0])
-              editor.insertText('  ')
-              expect(editor.lineForBufferRow(12)).toBe '  };'
-              editor.insertText('\t\t')
-              expect(editor.lineForBufferRow(12)).toBe '  \t\t};'
-
-          describe "when the current line does not match a decrease indent pattern", ->
-            it "leaves the line unchanged", ->
-              editor.setCursorBufferPosition([2, 4])
-              expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
-              editor.insertText('foo')
-              expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
-
-      describe "editor.normalizeIndentOnPaste", ->
-        beforeEach ->
-          atom.config.set('editor.normalizeIndentOnPaste', true)
-
-        it "does not normalize the indentation level of the text when editor.normalizeIndentOnPaste is false", ->
-          copyText("   function() {\nvar cool = 1;\n  }\n")
-          atom.config.set('editor.normalizeIndentOnPaste', false)
-          editor.setCursorBufferPosition([5, 2])
-          editor.pasteText()
-          expect(editor.lineForBufferRow(5)).toBe "     function() {"
-          expect(editor.lineForBufferRow(6)).toBe "var cool = 1;"
-          expect(editor.lineForBufferRow(7)).toBe "  }"
-
-        describe "when the inserted text contains no newlines", ->
-          it "does not adjust the indentation level of the text", ->
-            editor.setCursorBufferPosition([5, 2])
-            editor.insertText("foo", indentBasis: 5)
-            expect(editor.lineForBufferRow(5)).toBe "  foo    current = items.shift();"
-
-          it "does not adjust the whitespace if there are preceding characters", ->
-            copyText(" foo")
-            editor.setCursorBufferPosition([5, 30])
-            editor.pasteText()
-
-            expect(editor.lineForBufferRow(5)).toBe "      current = items.shift(); foo"
-
-        describe "when the inserted text contains newlines", ->
-          describe "when the cursor is preceded only by whitespace characters", ->
-            it "normalizes indented lines to the cursor's current indentation level", ->
-              copyText("    while (true) {\n      foo();\n    }\n", {startColumn: 2})
-              editor.setCursorBufferPosition([3, 4])
-              editor.pasteText()
-
-              expect(editor.lineForBufferRow(3)).toBe "    while (true) {"
-              expect(editor.lineForBufferRow(4)).toBe "      foo();"
-              expect(editor.lineForBufferRow(5)).toBe "    }"
-              expect(editor.lineForBufferRow(6)).toBe "var pivot = items.shift(), current, left = [], right = [];"
-
-          describe "when the cursor is preceded by non-whitespace characters", ->
-            it "normalizes the indentation level of all lines based on the level of the existing first line", ->
-              copyText("    while (true) {\n      foo();\n    }\n", {startColumn: 0})
-              editor.setCursorBufferPosition([1, Infinity])
-              editor.pasteText()
-
-              expect(editor.lineForBufferRow(1)).toBe "  var sort = function(items) {while (true) {"
-              expect(editor.lineForBufferRow(2)).toBe "    foo();"
-              expect(editor.lineForBufferRow(3)).toBe "  }"
-              expect(editor.lineForBufferRow(4)).toBe ""
-
-      it "autoIndentSelectedRows auto-indents the selection", ->
-        editor.setCursorBufferPosition([2, 0])
-        editor.insertText("function() {\ninside=true\n}\n  i=1\n")
-        editor.getSelection().setBufferRange([[2,0], [6,0]])
-        editor.autoIndentSelectedRows()
-
-        expect(editor.lineForBufferRow(2)).toBe "    function() {"
-        expect(editor.lineForBufferRow(3)).toBe "      inside=true"
-        expect(editor.lineForBufferRow(4)).toBe "    }"
-        expect(editor.lineForBufferRow(5)).toBe "    i=1"
-
-    describe ".destroy()", ->
-      it "destroys all markers associated with the edit session", ->
-        expect(buffer.getMarkerCount()).toBeGreaterThan 0
-        editor.destroy()
-        expect(buffer.getMarkerCount()).toBe 0
-
-    describe ".joinLines()", ->
-      describe "when no text is selected", ->
-        describe "when the line below isn't empty", ->
-          it "joins the line below with the current line separated by a space and moves the cursor to the start of line that was moved up", ->
-            editor.joinLines()
-            expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () { var sort = function(items) {'
-            expect(editor.getCursorBufferPosition()).toEqual [0, 30]
-
-        describe "when the line below is empty", ->
-          it "deletes the line below and moves the cursor to the end of the line", ->
-            editor.setCursorBufferPosition([9])
-            editor.joinLines()
-            expect(editor.lineForBufferRow(9)).toBe '  };'
-            expect(editor.lineForBufferRow(10)).toBe '  return sort(Array.apply(this, arguments));'
-            expect(editor.getCursorBufferPosition()).toEqual [9, 4]
-
-        describe "when the cursor is on the last row", ->
-          it "does nothing", ->
-            editor.setCursorBufferPosition([Infinity, Infinity])
-            editor.joinLines()
-            expect(editor.lineForBufferRow(12)).toBe '};'
-
-      describe "when text is selected", ->
-        describe "when the selection does not span multiple lines", ->
-          it "joins the line below with the current line separated by a space and retains the selected text", ->
-            editor.setSelectedBufferRange([[0, 1], [0, 3]])
-            editor.joinLines()
-            expect(editor.lineForBufferRow(0)).toBe 'var quicksort = function () { var sort = function(items) {'
-            expect(editor.getSelectedBufferRange()).toEqual [[0, 1], [0, 3]]
-
-        describe "when the selection spans multiple lines", ->
-          it "joins all selected lines separated by a space and retains the selected text", ->
-            editor.setSelectedBufferRange([[9, 3], [12, 1]])
-            editor.joinLines()
-            expect(editor.lineForBufferRow(9)).toBe '  }; return sort(Array.apply(this, arguments)); };'
-            expect(editor.getSelectedBufferRange()).toEqual [[9, 3], [9, 49]]
-
-    describe ".shouldPromptToSave()", ->
-      it "returns false when an edit session's buffer is in use by more than one session", ->
-        jasmine.unspy(editor, 'shouldPromptToSave')
-        expect(editor.shouldPromptToSave()).toBeFalsy()
-        buffer.setText('changed')
-        expect(editor.shouldPromptToSave()).toBeTruthy()
-        editor2 = atom.project.openSync('sample.js', autoIndent: false)
-        expect(editor.shouldPromptToSave()).toBeFalsy()
-        editor2.destroy()
-        expect(editor.shouldPromptToSave()).toBeTruthy()
-
-    describe "when the edit session contains surrogate pair characters", ->
-      it "correctly backspaces over them", ->
-        editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97')
-        editor.moveCursorToBottom()
-        editor.backspace()
-        expect(editor.getText()).toBe '\uD835\uDF97\uD835\uDF97'
-        editor.backspace()
-        expect(editor.getText()).toBe '\uD835\uDF97'
-        editor.backspace()
-        expect(editor.getText()).toBe ''
-
-      it "correctly deletes over them", ->
-        editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97')
-        editor.moveCursorToTop()
-        editor.delete()
-        expect(editor.getText()).toBe '\uD835\uDF97\uD835\uDF97'
-        editor.delete()
-        expect(editor.getText()).toBe '\uD835\uDF97'
-        editor.delete()
-        expect(editor.getText()).toBe ''
-
-      it "correctly moves over them", ->
-        editor.setText('\uD835\uDF97\uD835\uDF97\uD835\uDF97\n')
-        editor.moveCursorToTop()
-        editor.moveCursorRight()
-        expect(editor.getCursorBufferPosition()).toEqual [0, 2]
-        editor.moveCursorRight()
-        expect(editor.getCursorBufferPosition()).toEqual [0, 4]
-        editor.moveCursorRight()
-        expect(editor.getCursorBufferPosition()).toEqual [0, 6]
-        editor.moveCursorRight()
-        expect(editor.getCursorBufferPosition()).toEqual [1, 0]
-        editor.moveCursorLeft()
-        expect(editor.getCursorBufferPosition()).toEqual [0, 6]
-        editor.moveCursorLeft()
-        expect(editor.getCursorBufferPosition()).toEqual [0, 4]
-        editor.moveCursorLeft()
-        expect(editor.getCursorBufferPosition()).toEqual [0, 2]
-        editor.moveCursorLeft()
-        expect(editor.getCursorBufferPosition()).toEqual [0, 0]
-
-    describe ".setIndentationForBufferRow", ->
-      describe "when the editor uses soft tabs but the row has hard tabs", ->
-        it "only replaces whitespace charachters", ->
-          editor.setSoftWrap(true)
-          editor.setText("	1\n	2")
-          editor.setCursorBufferPosition([0, 0])
-          editor.setIndentationForBufferRow(0, 2)
-          expect(editor.getText()).toBe("    1\n	2")
-
-    describe ".reloadGrammar()", ->
-      beforeEach ->
-        waitsForPromise ->
-          atom.packages.activatePackage('language-coffee-script')
-
-      it "updates the grammar based on grammar overrides", ->
-        expect(editor.getGrammar().name).toBe 'JavaScript'
-        atom.syntax.setGrammarOverrideForPath(editor.getPath(), 'source.coffee')
-        editor.reloadGrammar()
-        expect(editor.getGrammar().name).toBe 'CoffeeScript'
-
-  describe "when the editor's grammar has an injection selector", ->
+        editor.setIndentationForBufferRow(0, 2)
+        expect(editor.getText()).toBe("    1\n	2")
+
+  describe ".reloadGrammar()", ->
     beforeEach ->
-
       waitsForPromise ->
-        atom.packages.activatePackage('language-text')
-
-      waitsForPromise ->
-        atom.packages.activatePackage('language-javascript')
-
-    it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
+        atom.packages.activatePackage('language-coffee-script')
+
+    it "updates the grammar based on grammar overrides", ->
+      expect(editor.getGrammar().name).toBe 'JavaScript'
+      atom.syntax.setGrammarOverrideForPath(editor.getPath(), 'source.coffee')
+      editor.reloadGrammar()
+      expect(editor.getGrammar().name).toBe 'CoffeeScript'
+
+describe "when the editor's grammar has an injection selector", ->
+  beforeEach ->
+
+    waitsForPromise ->
+      atom.packages.activatePackage('language-text')
+
+    waitsForPromise ->
+      atom.packages.activatePackage('language-javascript')
+
+  it "includes the grammar's patterns when the selector matches the current scope in other grammars", ->
+    waitsForPromise ->
+      atom.packages.activatePackage('language-hyperlink')
+
+    runs ->
+      grammar = atom.syntax.selectGrammar("text.js")
+      {tokens} = grammar.tokenizeLine("var i; // http://github.com")
+
+      expect(tokens[0].value).toBe "var"
+      expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
+
+      expect(tokens[6].value).toBe "http://github.com"
+      expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
+
+  describe "when the grammar is added", ->
+    it "retokenizes existing buffers that contain tokens that match the injection selector", ->
+      editor = atom.project.openSync('sample.js')
+      editor.setText("// http://github.com")
+
+      {tokens} = editor.lineForScreenRow(0)
+      expect(tokens[1].value).toBe " http://github.com"
+      expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
+
       waitsForPromise ->
         atom.packages.activatePackage('language-hyperlink')

       runs ->
-        grammar = atom.syntax.selectGrammar("text.js")
-        {tokens} = grammar.tokenizeLine("var i; // http://github.com")
-
-        expect(tokens[0].value).toBe "var"
-        expect(tokens[0].scopes).toEqual ["source.js", "storage.modifier.js"]
-
-        expect(tokens[6].value).toBe "http://github.com"
-        expect(tokens[6].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
-
-    describe "when the grammar is added", ->
-      it "retokenizes existing buffers that contain tokens that match the injection selector", ->
-        editor = atom.project.openSync('sample.js')
-        editor.setText("// http://github.com")
-
         {tokens} = editor.lineForScreenRow(0)
-        expect(tokens[1].value).toBe " http://github.com"
-        expect(tokens[1].scopes).toEqual ["source.js", "comment.line.double-slash.js"]
-
-        waitsForPromise ->
-          atom.packages.activatePackage('language-hyperlink')
-
-        runs ->
-          {tokens} = editor.lineForScreenRow(0)
-          expect(tokens[2].value).toBe "http://github.com"
-          expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]
+        expect(tokens[2].value).toBe "http://github.com"
+        expect(tokens[2].scopes).toEqual ["source.js", "comment.line.double-slash.js", "markup.underline.link.http.hyperlink"]

     describe "when the grammar is updated", ->
       it "retokenizes existing buffers that contain tokens that match the injection selector", ->