         expect(range.end).toEqual({row: 5, column: 27})
         expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
 
-    describe "shift-click", ->
-      beforeEach ->
-        editor.attachToDom()
-        editor.css(position: 'absolute', top: 10, left: 10)
-        editor.setCursorScreenPosition([4, 7])
-
-      it "selects from the cursor's current location to the clicked location", ->
-        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true)
-        expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 24]]
-
-      describe "when it is a double-click", ->
-        it "expands the selection to include the double-clicked word", ->
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 1 })
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 2 })
-          expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 27]]
-
-      describe "when it is a triple-click", ->
-        it "expands the selection to include the triple-clicked line", ->
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 1 })
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 2 })
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 3 })
-          expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 30]]
-
-  describe "multiple cursors", ->
-    it "places multiple cursors with meta-click", ->
+    describe "meta-click and drag", ->
+      it "adds an additional selection", ->
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 10])
+        editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [5, 27])
+        editor.renderedLines.trigger 'mouseup'
+
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [6, 10], metaKey: true)
+        editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [8, 27], metaKey: true)
+        editor.renderedLines.trigger 'mouseup'
+
+        selections = editor.getSelections()
+        expect(selections.length).toBe 2
+        [selection1, selection2] = selections
+        expect(selection1.getScreenRange()).toEqual [[4, 10], [5, 27]]
+        expect(selection2.getScreenRange()).toEqual [[6, 10], [8, 27]]
+
+  describe "when the cursor moves", ->
+    charWidth = null
+
+    beforeEach ->
       editor.attachToDom()
-      setEditorHeightInLines(editor, 5)
-      editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 0])
-      editor.scrollTop(editor.lineHeight * 6)
-
-      spyOn(editor, "scrollTo").andCallThrough()
-
-      editor.renderedLines.trigger mousedownEvent(editor: editor, point: [6, 0], metaKey: true)
-      expect(editor.scrollTo.callCount).toBe 1
-
-      [cursor1, cursor2] = editor.find('.cursor').map -> $(this).view()
-      expect(cursor1.position()).toEqual(top: 3 * editor.lineHeight, left: 0)
-      expect(cursor1.getBufferPosition()).toEqual [3, 0]
-      expect(cursor2.position()).toEqual(top: 6 * editor.lineHeight, left: 0)
-      expect(cursor2.getBufferPosition()).toEqual [6, 0]
-
-    describe "selections", ->
-      describe "upon clicking and dragging with the meta-key held down", ->
-        it "adds an additional selection upon clicking and dragging with the meta-key held down", ->
-          editor.attachToDom()
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [4, 10])
-          editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [5, 27])
-          editor.renderedLines.trigger 'mouseup'
-
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [6, 10], metaKey: true)
-          editor.renderedLines.trigger mousemoveEvent(editor: editor, point: [8, 27], metaKey: true)
-          editor.renderedLines.trigger 'mouseup'
-
-          selections = editor.getSelections()
-          expect(selections.length).toBe 2
-          [selection1, selection2] = selections
-          expect(selection1.getScreenRange()).toEqual [[4, 10], [5, 27]]
-          expect(selection2.getScreenRange()).toEqual [[6, 10], [8, 27]]
+      editor.vScrollMargin = 3
+      editor.hScrollMargin = 5
+      {charWidth} = editor
+
+    it "repositions the cursor's view on screen", ->
+      editor.attachToDom()
+      editor.setCursorScreenPosition(row: 2, column: 2)
+      expect(editor.getCursorView().position()).toEqual(top: 2 * editor.lineHeight, left: 2 * editor.charWidth)
+
+    describe "auto-scrolling", ->
+      it "only auto-scrolls when the last cursor is moved", ->
+        editor.setCursorBufferPosition([11,0])
+        editor.addCursorAtBufferPosition([6,50])
+        [cursor1, cursor2] = editor.getCursors()
+
+        spyOn(editor, 'scrollTo')
+        cursor1.setScreenPosition([10, 10])
+        expect(editor.scrollTo).not.toHaveBeenCalled()
+
+        cursor2.setScreenPosition([11, 11])
+        expect(editor.scrollTo).toHaveBeenCalled()
+
+      describe "when the last cursor exceeds the upper or lower scroll margins", ->
+        describe "when the editor is taller than twice the vertical scroll margin", ->
+          it "sets the scrollTop so the cursor remains within the scroll margin", ->
+            setEditorHeightInLines(editor, 10)
+
+            _.times 6, -> editor.moveCursorDown()
+            expect(editor.scrollTop()).toBe(0)
+
+            editor.moveCursorDown()
+            expect(editor.scrollTop()).toBe(editor.lineHeight)
+
+            editor.moveCursorDown()
+            expect(editor.scrollTop()).toBe(editor.lineHeight * 2)
+
+            _.times 3, -> editor.moveCursorUp()
+
+            editor.moveCursorUp()
+            expect(editor.scrollTop()).toBe(editor.lineHeight)
+
+            editor.moveCursorUp()
+            expect(editor.scrollTop()).toBe(0)
+
+        describe "when the editor is shorter than twice the vertical scroll margin", ->
+          it "sets the scrollTop based on a reduced scroll margin, which prevents a jerky tug-of-war between upper and lower scroll margins", ->
+            setEditorHeightInLines(editor, 5)
+
+            _.times 3, -> editor.moveCursorDown()
+
+            expect(editor.scrollTop()).toBe(editor.lineHeight)
+
+            editor.moveCursorUp()
+            expect(editor.renderedLines.css('top')).toBe "0px"
+
+      describe "when the last cursor exceeds the right or left scroll margins", ->
+        describe "when soft-wrap is disabled", ->
+          describe "when the editor is wider than twice the horizontal scroll margin", ->
+            it "sets the scrollView's scrollLeft so the cursor remains within the scroll margin", ->
+              setEditorWidthInChars(editor, 30)
+
+              # moving right
+              editor.setCursorScreenPosition([2, 24])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe 0
+
+              editor.setCursorScreenPosition([2, 25])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe charWidth
+
+              editor.setCursorScreenPosition([2, 28])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe charWidth * 4
+
+              # moving left
+              editor.setCursorScreenPosition([2, 9])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe charWidth * 4
+
+              editor.setCursorScreenPosition([2, 8])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe charWidth * 3
+
+              editor.setCursorScreenPosition([2, 5])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe 0
+
+          describe "when the editor is narrower than twice the horizontal scroll margin", ->
+            it "sets the scrollView's scrollLeft based on a reduced horizontal scroll margin, to prevent a jerky tug-of-war between right and left scroll margins", ->
+              editor.hScrollMargin = 6
+              setEditorWidthInChars(editor, 7)
+
+              editor.setCursorScreenPosition([2, 3])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe(0)
+
+              editor.setCursorScreenPosition([2, 4])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe(charWidth)
+
+              editor.setCursorScreenPosition([2, 3])
+              window.advanceClock()
+              expect(editor.scrollView.scrollLeft()).toBe(0)
+
+        describe "when soft-wrap is enabled", ->
+          beforeEach ->
+            editor.setSoftWrap(true)
+
+          it "does not scroll the buffer horizontally", ->
+            editor.width(charWidth * 30)
+
+            # moving right
+            editor.setCursorScreenPosition([2, 24])
+            expect(editor.scrollView.scrollLeft()).toBe 0
+
+            editor.setCursorScreenPosition([2, 25])
+            expect(editor.scrollView.scrollLeft()).toBe 0
+
+            editor.setCursorScreenPosition([2, 28])
+            expect(editor.scrollView.scrollLeft()).toBe 0
+
+            # moving left
+            editor.setCursorScreenPosition([2, 9])
+            expect(editor.scrollView.scrollLeft()).toBe 0
+
+            editor.setCursorScreenPosition([2, 8])
+            expect(editor.scrollView.scrollLeft()).toBe 0
+
+            editor.setCursorScreenPosition([2, 5])
+            expect(editor.scrollView.scrollLeft()).toBe 0
 
   describe "buffer manipulation", ->
     beforeEach ->

        expect(range.end).toEqual({row: 5, column: 27})
        expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)

      it "creates a selection from the word underneath an initial double click to mouse's new location ", ->
        editor.attachToDom()
        editor.css(position: 'absolute', top: 10, left: 10)

        expect(range.end).toEqual({row: 5, column: 27})
        expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)

    describe "trip-click and drag", ->
      it "creates a selection from the line underneath an initial triple click to mouse's new location ", ->
        editor.attachToDom()
        editor.css(position: 'absolute', top: 10, left: 10)
