         editor.setFontSize(10)
         expect(editor.renderedLines.find(".line").length).toBeGreaterThan originalLineCount
 
-  describe "when a mousedown event occurs in the editor", ->
+  describe "mouse events", ->
     beforeEach ->
       editor.attachToDom()
       editor.css(position: 'absolute', top: 10, left: 10)
 
-      describe "when it is a single click", ->
-        it "re-positions the cursor to the clicked row / column", ->
+    describe "single-click", ->
+      it "re-positions the cursor to the clicked row / column", ->
+        expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
+
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 10])
+        expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 10)
+
+      describe "when the lines are scrolled to the right", ->
+        it "re-positions the cursor on the clicked location", ->
+          setEditorWidthInChars(editor, 30)
           expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
-
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 10])
-          expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 10)
-
-        describe "when the lines are scrolled to the right", ->
-          it "re-positions the cursor on the clicked location", ->
-            setEditorWidthInChars(editor, 30)
-            expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
-            editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 30]) # scrolls lines to the right
-            editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 50])
-            expect(editor.getCursorBufferPosition()).toEqual(row: 3, column: 50)
-
-      describe "when it is a double click", ->
-        it "selects the word under the cursor", ->
-          expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 8], originalEvent: {detail: 1})
-          editor.renderedLines.trigger 'mouseup'
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 8], originalEvent: {detail: 2})
-          editor.renderedLines.trigger 'mouseup'
-          expect(editor.getSelectedText()).toBe "quicksort"
-
-      describe "when it is clicked more then twice (triple, quadruple, etc...)", ->
-        it "selects the line under the cursor", ->
-          expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
-
-          # Triple click
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 1})
-          editor.renderedLines.trigger 'mouseup'
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 2})
-          editor.renderedLines.trigger 'mouseup'
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 3})
-          editor.renderedLines.trigger 'mouseup'
-          expect(editor.getSelectedText()).toBe "  var sort = function(items) {"
-
-          # Quad click
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 1})
-          editor.renderedLines.trigger 'mouseup'
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 2})
-          editor.renderedLines.trigger 'mouseup'
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 3})
-          editor.renderedLines.trigger 'mouseup'
-          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 4})
-          editor.renderedLines.trigger 'mouseup'
-          expect(editor.getSelectedText()).toBe "    if (items.length <= 1) return items;"
-
-  describe "when the cursor moves", ->
-    charWidth = null
-
-    beforeEach ->
-      editor.attachToDom()
-      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 "selection", ->
-    selection = null
-
-    beforeEach ->
-      selection = editor.getSelection()
-
-    describe "when the mouse is dragged across the text", ->
+          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 30]) # scrolls lines to the right
+          editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 50])
+          expect(editor.getCursorBufferPosition()).toEqual(row: 3, column: 50)
+
+    describe "double-click", ->
+      it "selects the word under the cursor", ->
+        expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 8], originalEvent: {detail: 1})
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [0, 8], originalEvent: {detail: 2})
+        editor.renderedLines.trigger 'mouseup'
+        expect(editor.getSelectedText()).toBe "quicksort"
+
+    describe "triple/quardruple/etc-click", ->
+      it "selects the line under the cursor", ->
+        expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
+
+        # Triple click
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 1})
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 2})
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [1, 8], originalEvent: {detail: 3})
+        editor.renderedLines.trigger 'mouseup'
+        expect(editor.getSelectedText()).toBe "  var sort = function(items) {"
+
+        # Quad click
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 1})
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 2})
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 3})
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [2, 3], originalEvent: {detail: 4})
+        editor.renderedLines.trigger 'mouseup'
+        expect(editor.getSelectedText()).toBe "    if (items.length <= 1) return items;"
+
+    describe "shift-click", ->
+      it "selects from the cursor's current location to the clicked location", ->
+        editor.setCursorScreenPosition([4, 7])
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true)
+        expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 24]]
+
+    describe "shift-double-click", ->
+      it "expands the selection to include the double-clicked word", ->
+        editor.setCursorScreenPosition([4, 7])
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 1 })
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 2 })
+        editor.renderedLines.trigger 'mouseup'
+        expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 27]]
+
+    describe "shift-triple-click", ->
+      it "expands the selection to include the triple-clicked line", ->
+        editor.setCursorScreenPosition([4, 7])
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 1 })
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 2 })
+        editor.renderedLines.trigger 'mouseup'
+        editor.renderedLines.trigger mousedownEvent(editor: editor, point: [5, 24], shiftKey: true, originalEvent: { detail: 3 })
+        editor.renderedLines.trigger 'mouseup'
+        expect(editor.getSelection().getScreenRange()).toEqual [[4, 7], [5, 30]]
+
+    describe "meta-click", ->
+      it "places an additional cursor", ->
+        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.getCursorViews()
+        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 "click and drag", ->
       it "creates a selection from the initial click to mouse cursor's 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)

      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)

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

    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()
      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 ->
