text: /*
   * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   *
   */
  
  /*global describe, it, expect, beforeEach, afterEach, jasmine, spyOn */
  
  define(function (require, exports, module) {
      'use strict';
  
      var Editor              = require("editor/Editor").Editor,
          LanguageManager     = require("language/LanguageManager"),
          PreferencesManager  = require("preferences/PreferencesManager"),
          SpecRunnerUtils     = require("spec/SpecRunnerUtils");
  
      var langNames = {
          css:        {mode: "css",           langName: "CSS"},
          javascript: {mode: "javascript",    langName: "JavaScript"},
          html:       {mode: "html",          langName: "HTML"},
          unknown:    {mode: null,            langName: "Text"}
      };
  
      function compareMode(expected, actual) {
          if (typeof actual === "string") {
              return actual === expected;
          } else if (actual === null) {
              return expected === null;
          }
  
          return actual === expected;
      }
  
      function expectModeAndLang(editor, lang) {
          expect(editor.getModeForSelection()).toSpecifyModeNamed(lang.mode);
          expect(editor.getLanguageForSelection().getName()).toBe(lang.langName);
      }
  
      describe("Editor", function () {
          var defaultContent = "Brackets is going to be awesome!\n";
          var myDocument, myEditor;
  
          function createTestEditor(content, languageId) {
              // create dummy Document and Editor
              var mocks = SpecRunnerUtils.createMockEditor(content, languageId);
              myDocument = mocks.doc;
              myEditor = mocks.editor;
          }
  
          beforeEach(function () {
              this.addMatchers({
                  toSpecifyModeNamed: function (expected) {
                      return compareMode(expected, this.actual);
                  }
              });
          });
  
          afterEach(function () {
              if (myEditor) {
                  SpecRunnerUtils.destroyMockEditor(myDocument);
                  myEditor = null;
                  myDocument = null;
              }
          });
  
          // Helper functions for testing cursor position / selection range
          function fixPos(pos) {
              if (!("sticky" in pos)) {
                  pos.sticky = null;
              }
              return pos;
          }
          function fixSel(sel) {
              fixPos(sel.start);
              fixPos(sel.end);
              if (!("reversed" in sel)) {
                  sel.reversed = false;
              }
              return sel;
          }
          function fixSels(sels) {
              sels.forEach(function (sel) {
                  fixSel(sel);
              });
              return sels;
          }
          function expectCursorAt(pos) {
              var selection = myEditor.getSelection();
              expect(selection.start).toEqual(selection.end);
              expect(fixPos(selection.start)).toEqual(fixPos(pos));
          }
          function expectSelection(sel) {
              expect(fixSel(myEditor.getSelection())).toEqual(fixSel(sel));
          }
          function expectSelections(sels) {
              expect(fixSels(myEditor.getSelections())).toEqual(fixSels(sels));
          }
  
          describe("Editor wrapper", function () {
              beforeEach(function () {
                  createTestEditor(defaultContent, "");
              });
  
              it("should initialize with content", function () {
                  // verify editor content
                  expect(myEditor._codeMirror.getValue()).toEqual(defaultContent);
              });
  
              // FUTURE: this should really be in a Document unit test, but there's no "official"
              // way to create the model for a Document without manually creating an Editor, so we're
              // testing this here for now until we get a real central model.
              it("should trigger a synchronous Document change event when an edit is performed", function () {
                  var changeFired = false;
                  function changeHandler(event, doc, changeList) {
                      myDocument.off("change", changeHandler);
                      changeFired = true;
                      expect(doc).toBe(myDocument);
                      expect(changeList.length).toBe(1);
                      expect(changeList[0].from.line).toEqual(0);
                      expect(changeList[0].from.ch).toEqual(0);
                      expect(changeList[0].to.line).toEqual(1);
                      expect(changeList[0].to.ch).toEqual(0);
                      expect(changeList[0].text).toEqual(["new content"]);
                  }
                  myDocument.on("change", changeHandler);
                  myEditor._codeMirror.setValue("new content");
                  expect(changeFired).toBe(true);
              });
  
              it("should send an array of multiple change records for an operation", function () {
                  var cm = myEditor._codeMirror,
                      changeHandler = jasmine.createSpy();
                  myDocument.on("change", changeHandler);
                  cm.operation(function () {
                      cm.replaceRange("inserted", {line: 1, ch: 0});
                      cm.replaceRange("", {line: 0, ch: 0}, {line: 0, ch: 4});
                  });
  
                  expect(changeHandler.callCount).toBe(1);
  
                  var args = changeHandler.mostRecentCall.args;
                  expect(args[1]).toBe(myDocument);
                  expect(args[2][0].text).toEqual(["inserted"]);
                  expect(args[2][0].from.line).toEqual(1);
                  expect(args[2][0].from.ch).toEqual(0);
                  expect(args[2][0].to.line).toEqual(1);
                  expect(args[2][0].to.ch).toEqual(0);
                  expect(args[2][1].text).toEqual([""]);
                  expect(args[2][1].from.line).toEqual(0);
                  expect(args[2][1].from.ch).toEqual(0);
                  expect(args[2][1].to.line).toEqual(0);
                  expect(args[2][1].to.ch).toEqual(4);
              });
  
              it("should set mode based on Document language", function () {
                  createTestEditor(defaultContent, "html");
  
                  var htmlLanguage = LanguageManager.getLanguage("html");
                  expect(myEditor.getModeForDocument()).toBe(htmlLanguage.getMode());
              });
  
          });
  
          describe("Focus", function () {
              beforeEach(function () {
                  createTestEditor(defaultContent, "");
              });
  
              it("should not have focus until explicitly set", function () {
                  expect(myEditor.hasFocus()).toBe(false);
              });
              it("should be able to detect when it has focus", function () {
                  /*
                   * @note: This test really just ensures that
                   *          calling the editor's focus method
                   *          will call the codeMirror focus method
                   *        This is due to the fact that focusing the editor
                   *          may not actually focus the editor if the app
                   *          doesn't have the keyboard focus which changed
                   *          with cef 2171
                   * @todo: This will need to be replaced with
                   *       checks to see that the editor is the "active" element
                   *       rather than have the input focus after focusing
                   *       the editor
                   * @see: https://github.com/adobe/brackets/issues/9972
                   */
                  spyOn(myEditor._codeMirror, "focus").andCallThrough();
                  myEditor.focus();
                  expect(myEditor._codeMirror.focus).toHaveBeenCalled();
              });
          });
  
          describe("getModeForSelection()", function () {
              var jsContent = "var foo;";
              var htmlContent = "<html><head>\n" +
                                "  <script>\n" +
                                "    var bar;\n" +
                                "  </script>\n" +
                                "</head><body>\n" +
                                "  <p>Hello</p>\n" +
                                "</body></html>";
  
              it("should get mode in homogeneous file", function () {
                  createTestEditor(jsContent, langNames.javascript.mode);
  
                  // Mode at point
                  myEditor.setCursorPos(0, 0);    // first char in text
                  expectModeAndLang(myEditor, langNames.javascript);
                  myEditor.setCursorPos(0, 8);    // last char in text
                  expectModeAndLang(myEditor, langNames.javascript);
  
                  myEditor.setCursorPos(0, 3);    // middle of text
                  expectModeAndLang(myEditor, langNames.javascript);
  
                  // Mode for range
                  myEditor.setSelection({line: 0, ch: 4}, {line: 0, ch: 7});
                  expectModeAndLang(myEditor, langNames.javascript);
                  myEditor.setSelection({line: 0, ch: 0}, {line: 0, ch: 8});  // select all
                  expectModeAndLang(myEditor, langNames.javascript);
  
                  // Mode for multiple cursors/selections
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}},
                                          {start: {line: 0, ch: 5}, end: {line: 0, ch: 5}}]);
                  expectModeAndLang(myEditor, langNames.javascript);
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 0, ch: 3}},
                                          {start: {line: 0, ch: 5}, end: {line: 0, ch: 7}}]);
                  expectModeAndLang(myEditor, langNames.javascript);
              });
  
              it("should get mode in HTML file", function () {
                  createTestEditor(htmlContent, "html");
  
                  // Mode at point
                  myEditor.setCursorPos(0, 0);    // first char in text
                  expectModeAndLang(myEditor, langNames.html);
                  myEditor.setCursorPos(6, 14);    // last char in text
                  expectModeAndLang(myEditor, langNames.html);
  
                  myEditor.setCursorPos(5, 7);    // middle of text - html
                  expectModeAndLang(myEditor, langNames.html);
                  myEditor.setCursorPos(2, 7);    // middle of text - js
                  expectModeAndLang(myEditor, langNames.javascript);
  
                  // Mode for range - homogeneous mode
                  myEditor.setSelection({line: 5, ch: 2}, {line: 5, ch: 14});
                  expectModeAndLang(myEditor, langNames.html);
                  myEditor.setSelection({line: 5, ch: 0}, {line: 6, ch: 0});  // whole line
                  expectModeAndLang(myEditor, langNames.html);
                  myEditor.setSelection({line: 2, ch: 4}, {line: 2, ch: 12});
                  expectModeAndLang(myEditor, langNames.javascript);
                  myEditor.setSelection({line: 2, ch: 0}, {line: 3, ch: 0});  // whole line
                  expectModeAndLang(myEditor, langNames.javascript);
  
                  // Mode for multiple cursors/selections - homogeneous mode
                  myEditor.setSelections([{start: {line: 2, ch: 0}, end: {line: 2, ch: 0}},
                                          {start: {line: 2, ch: 4}, end: {line: 2, ch: 4}}]);
                  expectModeAndLang(myEditor, langNames.javascript);
                  myEditor.setSelections([{start: {line: 2, ch: 0}, end: {line: 2, ch: 2}},
                                          {start: {line: 2, ch: 4}, end: {line: 2, ch: 6}}]);
                  expectModeAndLang(myEditor, langNames.javascript);
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}},
                                          {start: {line: 5, ch: 7}, end: {line: 5, ch: 7}},
                                          {start: {line: 6, ch: 14}, end: {line: 6, ch: 14}}]);
                  expectModeAndLang(myEditor, langNames.html);
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 0, ch: 2}},
                                          {start: {line: 5, ch: 7}, end: {line: 5, ch: 9}},
                                          {start: {line: 6, ch: 12}, end: {line: 6, ch: 14}}]);
                  expectModeAndLang(myEditor, langNames.html);
  
                  // Mode for range - mix of modes
                  myEditor.setSelection({line: 2, ch: 4}, {line: 3, ch: 7});
                  expectModeAndLang(myEditor, langNames.unknown);
  
                  // Mode for multiple cursors/selections - mix of modes
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}},
                                          {start: {line: 2, ch: 4}, end: {line: 2, ch: 4}},
                                          {start: {line: 6, ch: 14}, end: {line: 6, ch: 14}}]);
                  expectModeAndLang(myEditor, langNames.unknown);
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 0, ch: 2}},
                                          {start: {line: 2, ch: 4}, end: {line: 2, ch: 7}},
                                          {start: {line: 6, ch: 12}, end: {line: 6, ch: 14}}]);
                  expectModeAndLang(myEditor, langNames.unknown);
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 2, ch: 0}},
                                          {start: {line: 2, ch: 4}, end: {line: 2, ch: 7}},
                                          {start: {line: 6, ch: 12}, end: {line: 6, ch: 14}}]);
                  expectModeAndLang(myEditor, langNames.unknown);
                  myEditor.setSelections([{start: {line: 0, ch: 0}, end: {line: 0, ch: 2}},
                                          {start: {line: 2, ch: 4}, end: {line: 5, ch: 3}},
                                          {start: {line: 6, ch: 12}, end: {line: 6, ch: 14}}]);
                  expectModeAndLang(myEditor, langNames.unknown);
  
                  // Mode for range - mix of modes where start & endpoints are same mode
                  // Known limitation of getModeForSelection() that it does not spot where the mode
                  // differs in mid-selection
                  myEditor.setSelection({line: 0, ch: 0}, {line: 6, ch: 14});  // select all
                  expectModeAndLang(myEditor, langNames.html);
              });
  
          });
  
          describe("Column/ch conversion", function () {
              it("should get mode in HTML file", function () {
                  var content =
                      "foo () {\n" +
                      "    one;\n" +
                      "\ttwo;\n" +
                      "}\n" +
                      "\n" +
                      "\tA\tB";
                  createTestEditor(content, "javascript");
  
                  // Tab size 4
  
                  expect(myEditor.getColOffset({line: 1, ch: 0})).toBe(0);
                  expect(myEditor.getColOffset({line: 1, ch: 1})).toBe(1);
                  expect(myEditor.getColOffset({line: 1, ch: 2})).toBe(2);
                  expect(myEditor.getColOffset({line: 1, ch: 3})).toBe(3);
                  expect(myEditor.getColOffset({line: 1, ch: 4})).toBe(4);
                  expect(myEditor.getColOffset({line: 1, ch: 5})).toBe(5);
                  expect(myEditor.getColOffset({line: 2, ch: 0})).toBe(0);
                  expect(myEditor.getColOffset({line: 2, ch: 1})).toBe(4);
                  expect(myEditor.getColOffset({line: 2, ch: 2})).toBe(5);
                  expect(myEditor.getColOffset({line: 4, ch: 0})).toBe(0);
                  expect(myEditor.getColOffset({line: 5, ch: 1})).toBe(4);
                  expect(myEditor.getColOffset({line: 5, ch: 2})).toBe(5);
                  expect(myEditor.getColOffset({line: 5, ch: 3})).toBe(8);
                  expect(myEditor.getColOffset({line: 5, ch: 4})).toBe(9);
  
                  // Tab size 2
                  var defaultTabSize = Editor.getTabSize();
                  expect(defaultTabSize).toBe(4);
                  Editor.setTabSize(2);
  
                  expect(myEditor.getColOffset({line: 1, ch: 0})).toBe(0);  // first line is all spaces: should be unchanged
                  expect(myEditor.getColOffset({line: 1, ch: 1})).toBe(1);
                  expect(myEditor.getColOffset({line: 1, ch: 2})).toBe(2);
                  expect(myEditor.getColOffset({line: 1, ch: 3})).toBe(3);
                  expect(myEditor.getColOffset({line: 1, ch: 4})).toBe(4);
                  expect(myEditor.getColOffset({line: 1, ch: 5})).toBe(5);
                  expect(myEditor.getColOffset({line: 2, ch: 0})).toBe(0);  // but line with a tab shows different behavior
                  expect(myEditor.getColOffset({line: 2, ch: 1})).toBe(2);
                  expect(myEditor.getColOffset({line: 2, ch: 2})).toBe(3);
                  expect(myEditor.getColOffset({line: 4, ch: 0})).toBe(0);
                  expect(myEditor.getColOffset({line: 5, ch: 1})).toBe(2);  // same here
                  expect(myEditor.getColOffset({line: 5, ch: 2})).toBe(3);
                  expect(myEditor.getColOffset({line: 5, ch: 3})).toBe(4);
                  expect(myEditor.getColOffset({line: 5, ch: 4})).toBe(5);
  
                  // Restore default
                  Editor.setTabSize(defaultTabSize);
              });
          });
  
          function makeDummyLines(num) {
              var content = [], i;
              for (i = 0; i < num; i++) {
                  content.push("this is line " + i);
              }
              return content;
          }
  
          describe("Selections", function () {
  
              beforeEach(function () {
                  createTestEditor(makeDummyLines(10).join("\n"), "unknown");
              });
  
              describe("hasSelection", function () {
                  it("should return false for a single cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      expect(myEditor.hasSelection()).toBe(false);
                  });
  
                  it("should return true for a single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 1}, {line: 0, ch: 5});
                      expect(myEditor.hasSelection()).toBe(true);
                  });
  
                  it("should return false for multiple cursors", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      expect(myEditor.hasSelection()).toBe(false);
                  });
  
                  it("should return true for multiple selections", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 2);
                      expect(myEditor.hasSelection()).toBe(true);
                  });
  
                  it("should return true for mixed cursors and selections", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      expect(myEditor.hasSelection()).toBe(true);
                  });
              });
  
              describe("getCursorPos", function () {
                  it("should return a single cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      expect(myEditor.getCursorPos().line).toEqual(0);
                      expect(myEditor.getCursorPos().ch).toEqual(2);
                      expect(myEditor.getCursorPos(false, "start").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "start").ch).toEqual(2);
                      expect(myEditor.getCursorPos(false, "anchor").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "anchor").ch).toEqual(2);
                      expect(myEditor.getCursorPos(false, "end").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "end").ch).toEqual(2);
                      expect(myEditor.getCursorPos(false, "head").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "head").ch).toEqual(2);
                  });
  
                  it("should return the correct ends of a single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 1}, {line: 0, ch: 5});
                      expect(myEditor.getCursorPos().line).toEqual(0);
                      expect(myEditor.getCursorPos().ch).toEqual(5);
                      expect(myEditor.getCursorPos(false, "start").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "start").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "anchor").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "anchor").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "end").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "end").ch).toEqual(5);
                      expect(myEditor.getCursorPos(false, "head").line).toEqual(0);
                      expect(myEditor.getCursorPos(false, "head").ch).toEqual(5);
                  });
  
                  it("should return the default primary cursor in a multiple cursor selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      expect(myEditor.getCursorPos().line).toEqual(2);
                      expect(myEditor.getCursorPos().ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "start").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "start").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "anchor").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "anchor").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "end").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "end").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "head").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "head").ch).toEqual(1);
                  });
  
                  it("should return the specific primary cursor in a multiple cursor selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 1);
                      expect(myEditor.getCursorPos().line).toEqual(1);
                      expect(myEditor.getCursorPos().ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "start").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "start").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "anchor").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "anchor").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "end").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "end").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "head").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "head").ch).toEqual(1);
                  });
  
                  it("should return the correct ends of the default primary selection in a multiple selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 2);
                      expect(myEditor.getCursorPos().line).toEqual(2);
                      expect(myEditor.getCursorPos().ch).toEqual(4);
                      expect(myEditor.getCursorPos(false, "start").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "start").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "anchor").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "anchor").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "end").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "end").ch).toEqual(4);
                      expect(myEditor.getCursorPos(false, "head").line).toEqual(2);
                      expect(myEditor.getCursorPos(false, "head").ch).toEqual(4);
                  });
  
                  it("should return the correct ends of a specific primary selection in a multiple selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 1);
                      expect(myEditor.getCursorPos().line).toEqual(1);
                      expect(myEditor.getCursorPos().ch).toEqual(4);
                      expect(myEditor.getCursorPos(false, "start").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "start").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "anchor").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "anchor").ch).toEqual(1);
                      expect(myEditor.getCursorPos(false, "end").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "end").ch).toEqual(4);
                      expect(myEditor.getCursorPos(false, "head").line).toEqual(1);
                      expect(myEditor.getCursorPos(false, "head").ch).toEqual(4);
                  });
              });
  
              describe("setCursorPos", function () {
                  it("should replace an existing single cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      myEditor.setCursorPos(1, 3);
                      expect(myEditor.getCursorPos().line).toEqual(1);
                      expect(myEditor.getCursorPos().ch).toEqual(3);
                  });
  
                  it("should replace an existing single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 1}, {line: 0, ch: 5});
                      myEditor.setCursorPos(1, 3);
                      expect(myEditor.getCursorPos().line).toEqual(1);
                      expect(myEditor.getCursorPos().ch).toEqual(3);
                  });
  
                  it("should replace existing multiple cursors", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      myEditor.setCursorPos(1, 3);
                      expect(myEditor.getCursorPos().line).toEqual(1);
                      expect(myEditor.getCursorPos().ch).toEqual(3);
                  });
  
                  it("should replace existing multiple selections", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 2);
                      myEditor.setCursorPos(1, 3);
                      expect(myEditor.getCursorPos().line).toEqual(1);
                      expect(myEditor.getCursorPos().ch).toEqual(3);
                  });
              });
  
              describe("getSelection", function () {
                  it("should return a single cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      expectSelection({start: {line: 0, ch: 2}, end: {line: 0, ch: 2}, reversed: false});
                  });
  
                  it("should return a single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 1}, {line: 0, ch: 5});
                      expectSelection({start: {line: 0, ch: 1}, end: {line: 0, ch: 5}, reversed: false});
                  });
  
                  it("should return a multiline selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 5}, {line: 1, ch: 3});
                      expectSelection({start: {line: 0, ch: 5}, end: {line: 1, ch: 3}, reversed: false});
                  });
  
                  it("should return a single selection in the proper order when reversed", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 5}, {line: 0, ch: 1});
                      expectSelection({start: {line: 0, ch: 1}, end: {line: 0, ch: 5}, reversed: true});
                  });
  
                  it("should return a multiline selection in the proper order when reversed", function () {
                      myEditor._codeMirror.setSelection({line: 1, ch: 3}, {line: 0, ch: 5});
                      expectSelection({start: {line: 0, ch: 5}, end: {line: 1, ch: 3}, reversed: true});
                  });
  
                  it("should return the default primary cursor in a multiple cursor selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      expectSelection({start: {line: 2, ch: 1}, end: {line: 2, ch: 1}, reversed: false});
                  });
  
                  it("should return the specific primary cursor in a multiple cursor selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 1);
                      expectSelection({start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, reversed: false});
                  });
  
                  it("should return the default primary selection in a multiple selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 2);
                      expectSelection({start: {line: 2, ch: 1}, end: {line: 2, ch: 4}, reversed: false});
                  });
  
                  it("should return the default primary selection in the proper order when reversed", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 4}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      expectSelection({start: {line: 2, ch: 1}, end: {line: 2, ch: 4}, reversed: true});
                  });
  
                  it("should return the specific primary selection in a multiple selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 1);
                      expectSelection({start: {line: 1, ch: 1}, end: {line: 1, ch: 4}, reversed: false});
                  });
  
                  it("should return the specific primary selection in the proper order when reversed", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 4}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 1);
                      expectSelection({start: {line: 1, ch: 1}, end: {line: 1, ch: 4}, reversed: true});
                  });
  
              });
  
              describe("getSelections", function () {
                  it("should return a single cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      expectSelections([{start: {line: 0, ch: 2}, end: {line: 0, ch: 2}, reversed: false, primary: true}]);
                  });
  
                  it("should return a single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 1}, {line: 0, ch: 5});
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 0, ch: 5}, reversed: false, primary: true}]);
                  });
  
                  it("should properly reverse a single selection whose head is before its anchor", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 5}, {line: 0, ch: 1});
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 0, ch: 5}, reversed: true, primary: true}]);
                  });
  
                  it("should return multiple cursors", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 0, ch: 1}, reversed: false, primary: false},
                                                          {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, reversed: false, primary: false},
                                                          {start: {line: 2, ch: 1}, end: {line: 2, ch: 1}, reversed: false, primary: true}
                                                         ]);
                  });
  
                  it("should return a multiple selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 2);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 0, ch: 4}, reversed: false, primary: false},
                                                          {start: {line: 1, ch: 1}, end: {line: 1, ch: 4}, reversed: false, primary: false},
                                                          {start: {line: 2, ch: 1}, end: {line: 2, ch: 4}, reversed: false, primary: true}
                                                         ]);
                  });
  
                  it("should properly reverse selections whose heads are before their anchors in a multiple selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 4}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 4}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 0, ch: 4}, reversed: true, primary: false},
                                                          {start: {line: 1, ch: 1}, end: {line: 1, ch: 4}, reversed: false, primary: false},
                                                          {start: {line: 2, ch: 1}, end: {line: 2, ch: 4}, reversed: true, primary: true}
                                                         ]);
                  });
  
                  it("should properly reverse multiline selections whose heads are before their anchors in a multiple selection", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 1, ch: 3}, head: {line: 0, ch: 5}},
                                                          {anchor: {line: 4, ch: 4}, head: {line: 3, ch: 1}}
                                                         ], 1);
                      expectSelections([{start: {line: 0, ch: 5}, end: {line: 1, ch: 3}, reversed: true, primary: false},
                                                          {start: {line: 3, ch: 1}, end: {line: 4, ch: 4}, reversed: true, primary: true}
                                                         ]);
                  });
              });
  
              describe("getSelectedText", function () {
                  it("should return empty string for a cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      expect(myEditor.getSelectedText()).toEqual("");
                      expect(myEditor.getSelectedText(true)).toEqual("");
                  });
  
                  it("should return the contents of a single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 8}, {line: 0, ch: 14});
                      expect(myEditor.getSelectedText()).toEqual("line 0");
                      expect(myEditor.getSelectedText(true)).toEqual("line 0");
                  });
  
                  it("should return the primary selection by default, but concatenate contents if allSelections is true", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 8}, head: {line: 0, ch: 14}},
                                                          {anchor: {line: 1, ch: 8}, head: {line: 1, ch: 14}},
                                                          {anchor: {line: 2, ch: 8}, head: {line: 2, ch: 14}}
                                                         ], 2);
                      expect(myEditor.getSelectedText()).toEqual("line 2");
                      expect(myEditor.getSelectedText(true)).toEqual("line 0\nline 1\nline 2");
                  });
  
                  it("should return a primary selection other than the last", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 8}, head: {line: 0, ch: 14}},
                                                          {anchor: {line: 1, ch: 8}, head: {line: 1, ch: 14}},
                                                          {anchor: {line: 2, ch: 8}, head: {line: 2, ch: 14}}
                                                         ], 1);
                      expect(myEditor.getSelectedText()).toEqual("line 1");
                      expect(myEditor.getSelectedText(true)).toEqual("line 0\nline 1\nline 2");
                  });
  
                  it("should return the contents of a multiple selection when some selections are reversed", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 14}, head: {line: 0, ch: 8}},
                                                          {anchor: {line: 1, ch: 8}, head: {line: 1, ch: 14}},
                                                          {anchor: {line: 2, ch: 14}, head: {line: 2, ch: 8}}
                                                         ], 2);
                      expect(myEditor.getSelectedText()).toEqual("line 2");
                      expect(myEditor.getSelectedText(true)).toEqual("line 0\nline 1\nline 2");
                  });
              });
  
              describe("setSelection", function () {
                  it("should replace an existing single cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      myEditor.setSelection({line: 1, ch: 3}, {line: 2, ch: 5});
                      expectSelection({start: {line: 1, ch: 3}, end: {line: 2, ch: 5}, reversed: false});
                  });
  
                  it("should replace an existing single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 1}, {line: 0, ch: 5});
                      myEditor.setSelection({line: 1, ch: 3}, {line: 2, ch: 5});
                      expectSelection({start: {line: 1, ch: 3}, end: {line: 2, ch: 5}, reversed: false});
                  });
  
                  it("should allow implicit end", function () {
                      myEditor.setSelection({line: 1, ch: 3});
                      expectSelection({start: {line: 1, ch: 3}, end: {line: 1, ch: 3}, reversed: false});
                  });
  
                  it("should replace existing multiple cursors", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      myEditor.setSelection({line: 1, ch: 3}, {line: 2, ch: 5});
                      expectSelection({start: {line: 1, ch: 3}, end: {line: 2, ch: 5}, reversed: false});
                  });
  
                  it("should replace existing multiple selections", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 2);
                      myEditor.setSelection({line: 1, ch: 3}, {line: 2, ch: 5});
                      expectSelection({start: {line: 1, ch: 3}, end: {line: 2, ch: 5}, reversed: false});
                  });
              });
  
              describe("setSelections", function () {
                  it("should replace an existing single cursor", function () {
                      myEditor._codeMirror.setCursor(0, 2);
                      myEditor.setSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}}]);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: false, primary: false},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false, primary: true}]);
                      expectSelection({start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false});
                  });
  
                  it("should replace an existing single selection", function () {
                      myEditor._codeMirror.setSelection({line: 0, ch: 1}, {line: 0, ch: 5});
                      myEditor.setSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}}]);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: false, primary: false},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false, primary: true}]);
                      expectSelection({start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false});
                  });
  
                  it("should replace existing multiple cursors", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 1}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 1}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 1}}
                                                         ], 2);
                      myEditor.setSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}}]);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: false, primary: false},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false, primary: true}]);
                      expectSelection({start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false});
                  });
  
                  it("should replace existing multiple selections", function () {
                      myEditor._codeMirror.setSelections([{anchor: {line: 0, ch: 1}, head: {line: 0, ch: 4}},
                                                          {anchor: {line: 1, ch: 1}, head: {line: 1, ch: 4}},
                                                          {anchor: {line: 2, ch: 1}, head: {line: 2, ch: 4}}
                                                         ], 2);
                      myEditor.setSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}}]);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: false, primary: false},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false, primary: true}]);
                      expectSelection({start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false});
                  });
  
                  it("should specify non-default primary selection", function () {
                      myEditor.setSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, primary: true},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}}]);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: false, primary: true},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false, primary: false}]);
                      expectSelection({start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: false});
                  });
  
                  it("should sort and merge overlapping selections", function () {
                      myEditor.setSelections([{start: {line: 2, ch: 4}, end: {line: 3, ch: 0}},
                                              {start: {line: 2, ch: 3}, end: {line: 2, ch: 6}},
                                              {start: {line: 1, ch: 1}, end: {line: 1, ch: 4}}]);
                      expectSelections([{start: {line: 1, ch: 1}, end: {line: 1, ch: 4}, reversed: false, primary: true},
                                              {start: {line: 2, ch: 3}, end: {line: 3, ch: 0}, reversed: false, primary: false}]);
                      expectSelection({start: {line: 1, ch: 1}, end: {line: 1, ch: 4}, reversed: false});
                  });
  
                  it("should properly set reversed selections", function () {
                      myEditor.setSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: true},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}}]);
                      expectSelections([{start: {line: 0, ch: 1}, end: {line: 1, ch: 3}, reversed: true, primary: false},
                                              {start: {line: 1, ch: 8}, end: {line: 2, ch: 5}, reversed: false, primary: true}]);
  
                  });
              });
  
              describe("convertToLineSelections", function () {
                  it("should expand a cursor to a line selection, keeping original selection for tracking", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(1);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                  });
  
                  it("should expand a range within a line to a line selection, keeping original selection for tracking", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 8}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(1);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                  });
  
                  it("should expand a range that spans multiple lines to a line selection", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 1, ch: 8}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(2);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                  });
  
                  it("should preserve the reversed attribute on a tracked range", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 8}, reversed: true}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(1);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                  });
  
                  it("should keep a line selection the same if expandEndAtStartOfLine is not set", function () {
                      var origSelections = [{start: {line: 0, ch: 0}, end: {line: 1, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(1);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                  });
  
                  it("should expand a line selection if expandEndAtStartOfLine is set", function () {
                      var origSelections = [{start: {line: 0, ch: 0}, end: {line: 1, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections, {expandEndAtStartOfLine: true});
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(2);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                  });
  
                  it("should process a discontiguous mix of cursor, range, and line selections separately, preserving the primary tracked selection", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}},
                                            {start: {line: 2, ch: 4}, end: {line: 2, ch: 8}, primary: true},
                                            {start: {line: 4, ch: 4}, end: {line: 5, ch: 8}},
                                            {start: {line: 7, ch: 0}, end: {line: 8, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(4);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(1);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[1].selectionForEdit.start.line).toEqual(2);
                      expect(result[1].selectionForEdit.start.ch).toEqual(0);
                      expect(result[1].selectionForEdit.end.line).toEqual(3);
                      expect(result[1].selectionForEdit.end.ch).toEqual(0);
                      expect(result[1].selectionsToTrack.length).toBe(1);
                      expect(result[1].selectionsToTrack[0]).toEqual(origSelections[1]);
                      expect(result[2].selectionForEdit.start.line).toEqual(4);
                      expect(result[2].selectionForEdit.start.ch).toEqual(0);
                      expect(result[2].selectionForEdit.end.line).toEqual(6);
                      expect(result[2].selectionForEdit.end.ch).toEqual(0);
                      expect(result[2].selectionsToTrack.length).toBe(1);
                      expect(result[2].selectionsToTrack[0]).toEqual(origSelections[2]);
                      expect(result[3].selectionForEdit.start.line).toEqual(7);
                      expect(result[3].selectionForEdit.start.ch).toEqual(0);
                      expect(result[3].selectionForEdit.end.line).toEqual(8);
                      expect(result[3].selectionForEdit.end.ch).toEqual(0); // not expanded since expandEndAtStartOfLine is false
                      expect(result[3].selectionsToTrack.length).toBe(1);
                      expect(result[3].selectionsToTrack[0]).toEqual(origSelections[3]);
                  });
  
                  it("should merge selections on same line, preserving primary/reversed info on subsumed selections", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}},
                                            {start: {line: 0, ch: 8}, end: {line: 1, ch: 8}, primary: true, reversed: true},
                                            {start: {line: 4, ch: 0}, end: {line: 5, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(2);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(2);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(2);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[0].selectionsToTrack[1]).toEqual(origSelections[1]);
                      expect(result[1].selectionForEdit.start.line).toEqual(4);
                      expect(result[1].selectionForEdit.start.ch).toEqual(0);
                      expect(result[1].selectionForEdit.end.line).toEqual(5);
                      expect(result[1].selectionForEdit.end.ch).toEqual(0);
                      expect(result[1].selectionsToTrack.length).toBe(1);
                      expect(result[1].selectionsToTrack[0]).toEqual(origSelections[2]);
                  });
  
                  it("should merge selections on adjacent lines by default", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}},
                                            {start: {line: 1, ch: 4}, end: {line: 1, ch: 4}, primary: true},
                                            {start: {line: 4, ch: 0}, end: {line: 5, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(2);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(2);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(2);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[0].selectionsToTrack[1]).toEqual(origSelections[1]);
                      expect(result[1].selectionForEdit.start.line).toEqual(4);
                      expect(result[1].selectionForEdit.start.ch).toEqual(0);
                      expect(result[1].selectionForEdit.end.line).toEqual(5);
                      expect(result[1].selectionForEdit.end.ch).toEqual(0);
                      expect(result[1].selectionsToTrack.length).toBe(1);
                      expect(result[1].selectionsToTrack[0]).toEqual(origSelections[2]);
                  });
  
                  it("should merge adjacent multiline selections where the first selection ends on the same line where the second selection starts", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 1, ch: 4}, primary: true},
                                            {start: {line: 1, ch: 8}, end: {line: 2, ch: 8}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(3);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(2);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[0].selectionsToTrack[1]).toEqual(origSelections[1]);
                  });
  
                  it("should not merge selections on adjacent lines if mergeAdjacent is false", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}},
                                            {start: {line: 1, ch: 4}, end: {line: 1, ch: 4}, primary: true},
                                            {start: {line: 4, ch: 0}, end: {line: 5, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections, {mergeAdjacent: false});
                      expect(result.length).toBe(3);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(1);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[1].selectionForEdit.start.line).toEqual(1);
                      expect(result[1].selectionForEdit.start.ch).toEqual(0);
                      expect(result[1].selectionForEdit.end.line).toEqual(2);
                      expect(result[1].selectionForEdit.end.ch).toEqual(0);
                      expect(result[1].selectionsToTrack.length).toBe(1);
                      expect(result[1].selectionsToTrack[0]).toEqual(origSelections[1]);
                      expect(result[2].selectionForEdit.start.line).toEqual(4);
                      expect(result[2].selectionForEdit.start.ch).toEqual(0);
                      expect(result[2].selectionForEdit.end.line).toEqual(5);
                      expect(result[2].selectionForEdit.end.ch).toEqual(0); // not expanded since expandEndAtStartOfLine not set
                      expect(result[2].selectionsToTrack.length).toBe(1);
                      expect(result[2].selectionsToTrack[0]).toEqual(origSelections[2]);
                  });
  
                  it("should merge line selections separated by a one-line gap by default if expandEndAtStartOfLine is true", function () {
                      var origSelections = [{start: {line: 0, ch: 0}, end: {line: 1, ch: 0}, primary: true},
                                            {start: {line: 2, ch: 0}, end: {line: 3, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections, {expandEndAtStartOfLine: true});
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(4);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(2);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[0].selectionsToTrack[1]).toEqual(origSelections[1]);
                  });
  
                  it("should not merge line selections separated by a one-line gap if expandEndAtStartOfLine is true but mergeAdjacent is false", function () {
                      // Note that in this case, if you were to actually set this as a multiple selection, CodeMirror would
                      // merge the adjacent selections at that point. But while processing an edit you might not want that.
                      var origSelections = [{start: {line: 0, ch: 0}, end: {line: 1, ch: 0}, primary: true},
                                            {start: {line: 2, ch: 0}, end: {line: 3, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections, {expandEndAtStartOfLine: true, mergeAdjacent: false});
                      expect(result.length).toBe(2);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(2);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(1);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[1].selectionForEdit.start.line).toEqual(2);
                      expect(result[1].selectionForEdit.start.ch).toEqual(0);
                      expect(result[1].selectionForEdit.end.line).toEqual(4);
                      expect(result[1].selectionForEdit.end.ch).toEqual(0);
                      expect(result[1].selectionsToTrack.length).toBe(1);
                      expect(result[1].selectionsToTrack[0]).toEqual(origSelections[1]);
                  });
  
                  it("should merge multiple adjacent/overlapping selections together", function () {
                      var origSelections = [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}},
                                            {start: {line: 1, ch: 4}, end: {line: 2, ch: 4}, primary: true, reversed: true},
                                            {start: {line: 2, ch: 8}, end: {line: 5, ch: 0}}],
                          result = myEditor.convertToLineSelections(origSelections);
                      expect(result.length).toBe(1);
                      expect(result[0].selectionForEdit.start.line).toEqual(0);
                      expect(result[0].selectionForEdit.start.ch).toEqual(0);
                      expect(result[0].selectionForEdit.end.line).toEqual(5);
                      expect(result[0].selectionForEdit.end.ch).toEqual(0);
                      expect(result[0].selectionsToTrack.length).toBe(3);
                      expect(result[0].selectionsToTrack[0]).toEqual(origSelections[0]);
                      expect(result[0].selectionsToTrack[1]).toEqual(origSelections[1]);
                      expect(result[0].selectionsToTrack[2]).toEqual(origSelections[2]);
                  });
              });
          });
  
          describe("Soft tabs", function () {
              beforeEach(function () {
                  // Configure the editor's CM instance for 4-space soft tabs, regardless of prefs.
                  createTestEditor("", "unknown");
                  var instance = myEditor._codeMirror;
                  instance.setOption("indentWithTabs", false);
                  instance.setOption("indentUnit", 4);
              });
  
              function checkSoftTab(sel, dir, command, expectedSel, expectedText) {
                  expectedText = expectedText || myEditor.document.getText();
  
                  if (Array.isArray(sel)) {
                      myEditor.setSelections(sel);
                  } else {
                      myEditor.setCursorPos(sel);
                  }
  
                  myEditor._handleSoftTabNavigation(dir, command);
  
                  if (Array.isArray(expectedSel)) {
                      expectSelections(expectedSel);
                  } else {
                      expectCursorAt(expectedSel);
                  }
                  expect(myEditor.document.getText()).toEqual(expectedText);
              }
  
              it("should move left by a soft tab if cursor is immediately after 1 indent level worth of spaces at beginning of line", function () {
                  myEditor.document.setText("    content");
                  checkSoftTab({line: 0, ch: 4}, -1, "moveH", {line: 0, ch: 0});
              });
              it("should backspace by a soft tab if cursor is immediately after 1 indent level worth of spaces at beginning of line", function () {
                  myEditor.document.setText("    content");
                  checkSoftTab({line: 0, ch: 4}, -1, "deleteH", {line: 0, ch: 0}, "content");
              });
              it("should move right by a soft tab if cursor is immediately before 1 indent level worth of spaces at beginning of line", function () {
                  myEditor.document.setText("    content");
                  checkSoftTab({line: 0, ch: 0}, 1, "moveH", {line: 0, ch: 4});
              });
              it("should delete right by a soft tab if cursor is immediately before 1 indent level worth of spaces at beginning of line", function () {
                  myEditor.document.setText("    content");
                  checkSoftTab({line: 0, ch: 0}, 1, "deleteH", {line: 0, ch: 0}, "content");
              });
  
              it("should move left by a soft tab if cursor is immediately after 2 indent levels worth of spaces at beginning of line", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 8}, -1, "moveH", {line: 0, ch: 4});
              });
              it("should backspace by a soft tab if cursor is immediately after 2 indent levels worth of spaces at beginning of line", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 8}, -1, "deleteH", {line: 0, ch: 4}, "    content");
              });
              it("should move right by a soft tab if cursor is immediately before 2 indent levels worth of spaces at beginning of line", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 0}, 1, "moveH", {line: 0, ch: 4});
              });
              it("should delete right by a soft tab if cursor is immediately before 2 indent levels worth of spaces at beginning of line", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 0}, 1, "deleteH", {line: 0, ch: 0}, "    content");
              });
              it("should move left by a soft tab if cursor is exactly between 2 indent levels worth of spaces", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 4}, -1, "moveH", {line: 0, ch: 0});
              });
              it("should backspace by a soft tab if cursor is exactly between 2 indent levels worth of spaces", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 4}, -1, "deleteH", {line: 0, ch: 0}, "    content");
              });
              it("should move right by a soft tab if cursor is exactly between 2 indent levels worth of spaces", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 4}, 1, "moveH", {line: 0, ch: 8});
              });
              it("should delete right by a soft tab if cursor is exactly between 2 indent levels worth of spaces", function () {
                  myEditor.document.setText("        content");
                  checkSoftTab({line: 0, ch: 4}, 1, "deleteH", {line: 0, ch: 4}, "    content");
              });
  
              it("should move left to tab stop if cursor is 1 char after it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 5}, -1, "moveH", {line: 0, ch: 4});
              });
              it("should backspace to tab stop if cursor is 1 char after it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 5}, -1, "deleteH", {line: 0, ch: 4}, "       ");
              });
              it("should move right to tab stop if cursor is 1 char before it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 3}, 1, "moveH", {line: 0, ch: 4});
              });
              it("should delete right to tab stop if cursor is 1 char before it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 3}, 1, "deleteH", {line: 0, ch: 3}, "       ");
              });
              it("should move left to tab stop if cursor is 2 chars after it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 6}, -1, "moveH", {line: 0, ch: 4});
              });
              it("should backspace to tab stop if cursor is 2 chars after it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 6}, -1, "deleteH", {line: 0, ch: 4}, "      ");
              });
              it("should move right to tab stop if cursor is 2 chars before it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 2}, 1, "moveH", {line: 0, ch: 4});
              });
              it("should delete right to tab stop if cursor is 2 chars before it", function () {
                  myEditor.document.setText("        ");
                  checkSoftTab({line: 0, ch: 2}, 1, "deleteH", {line: 0, ch: 2}, "      ");
              });
  
              it("should not handle soft tab if moving left after non-whitespace content", function () {
                  myEditor.document.setText("start   content");
                  checkSoftTab({line: 0, ch: 8}, -1, "moveH", {line: 0, ch: 7});
              });
              it("should not handle soft tab if moving right after non-whitespace content", function () {
                  myEditor.document.setText("start   content");
                  checkSoftTab({line: 0, ch: 5}, 1, "moveH", {line: 0, ch: 6});
              });
              it("should not handle soft tab if moving left at beginning of line", function () {
                  myEditor.document.setText("foo\n    content");
                  checkSoftTab({line: 1, ch: 0}, -1, "moveH", {line: 0, ch: 3});
              });
              it("should not handle soft tab if moving right at end of line", function () {
                  myEditor.document.setText("    content\nfoo");
                  checkSoftTab({line: 0, ch: 11}, 1, "moveH", {line: 1, ch: 0});
              });
              it("should not handle soft tab if moving right at end of line would cause a jump past end of line", function () {
                  myEditor.document.setText("   four  ");
                  checkSoftTab({line: 0, ch: 8}, 1, "moveH", {line: 0, ch: 9});
              });
  
              describe("with soft tabs preference off", function () {
                  beforeEach(function () {
                      // Disable soft tabs
                      PreferencesManager.set("softTabs", false);
                  });
                  afterEach(function () {
                      // Re-enable soft tabs
                      PreferencesManager.set("softTabs", true);
                  });
  
                  it("should move left by 1 space if cursor is immediately after 1 indent level worth of spaces at beginning of line", function () {
                      myEditor.document.setText("    content");
                      checkSoftTab({line: 0, ch: 4}, -1, "moveH", {line: 0, ch: 3});
                  });
                  it("should backspace by 1 space if cursor is immediately after 1 indent level worth of spaces at beginning of line", function () {
                      myEditor.document.setText("    content");
                      checkSoftTab({line: 0, ch: 4}, -1, "deleteH", {line: 0, ch: 3}, "   content");
                  });
                  it("should move right by 1 space if cursor is immediately before 1 indent level worth of spaces at beginning of line", function () {
                      myEditor.document.setText("    content");
                      checkSoftTab({line: 0, ch: 0}, 1, "moveH", {line: 0, ch: 1});
                  });
                  it("should delete right by 1 space if cursor is immediately before 1 indent level worth of spaces at beginning of line", function () {
                      myEditor.document.setText("    content");
                      checkSoftTab({line: 0, ch: 0}, 1, "deleteH", {line: 0, ch: 0}, "   content");
                  });
              });
  
              describe("with multiple selections", function () {
                  it("should move left over a soft tab from multiple aligned cursors", function () {
                      myEditor.document.setText("    one\n    two\n    three\n");
                      checkSoftTab([{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}},
                                    {start: {line: 1, ch: 4}, end: {line: 1, ch: 4}},
                                    {start: {line: 2, ch: 4}, end: {line: 2, ch: 4}}],
                                   -1, "moveH",
                                   [{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 0}, end: {line: 2, ch: 0}, primary: true, reversed: false}]);
                  });
  
                  it("should move right over a soft tab from multiple aligned cursors", function () {
                      myEditor.document.setText("    one\n    two\n    three\n");
                      checkSoftTab([{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}},
                                    {start: {line: 2, ch: 0}, end: {line: 2, ch: 0}}],
                                   1, "moveH",
                                   [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 4}, end: {line: 1, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 4}, end: {line: 2, ch: 4}, primary: true, reversed: false}]);
                  });
  
                  it("should backspace over a soft tab from multiple aligned cursors", function () {
                      myEditor.document.setText("    one\n    two\n    three\n");
                      checkSoftTab([{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}},
                                    {start: {line: 1, ch: 4}, end: {line: 1, ch: 4}},
                                    {start: {line: 2, ch: 4}, end: {line: 2, ch: 4}}],
                                   -1, "deleteH",
                                   [{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 0}, end: {line: 2, ch: 0}, primary: true, reversed: false}],
                                  "one\ntwo\nthree\n");
                  });
  
                  it("should delete right over a soft tab from multiple aligned cursors", function () {
                      myEditor.document.setText("    one\n    two\n    three\n");
                      checkSoftTab([{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}},
                                    {start: {line: 2, ch: 0}, end: {line: 2, ch: 0}}],
                                   1, "deleteH",
                                   [{start: {line: 0, ch: 0}, end: {line: 0, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 0}, end: {line: 2, ch: 0}, primary: true, reversed: false}],
                                  "one\ntwo\nthree\n");
                  });
  
                  it("should move left to next soft tab from multiple cursors at same distance from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 2}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "moveH",
                                   [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, primary: true, reversed: false}]);
                  });
  
                  it("should move right to next soft tab from multiple cursors at same distance from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}}],
                                   1, "moveH",
                                   [{start: {line: 0, ch: 8}, end: {line: 0, ch: 8}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 4}, end: {line: 1, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 12}, end: {line: 2, ch: 12}, primary: true, reversed: false}]);
                  });
  
                  it("should backspace to next soft tab from multiple cursors at same distance from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 2}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "deleteH",
                                   [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, primary: true, reversed: false}],
                                   "      one\n      two\n          three\n");
                  });
  
                  it("should delete right to next soft tab from multiple cursors at same distance from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}}],
                                   1, "deleteH",
                                   [{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}, primary: true, reversed: false}],
                                   "     one\n     two\n         three\n"
                                  );
                  });
  
                  it("should do default move left from multiple cursors at different distances from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "moveH",
                                   [{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 0}, end: {line: 1, ch: 0}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}, primary: true, reversed: false}]);
                  });
  
                  it("should do default move right from multiple cursors at different distances from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   1, "moveH",
                                   [{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 2}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 11}, end: {line: 2, ch: 11}, primary: true, reversed: false}]);
                  });
  
                  it("should do default backspace from multiple cursors at different distances from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 2}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "deleteH",
                                   [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}, primary: true, reversed: false}],
                                   "       one\n       two\n           three\n");
                  });
  
                  it("should do default delete right from multiple cursors at different distances from tab stops", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}}],
                                   1, "deleteH",
                                   [{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, primary: true, reversed: false}],
                                   "       one\n       two\n           three\n"
                                  );
                  });
  
                  it("should do default move left from multiple cursors if one is inside content", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}},
                                    {start: {line: 1, ch: 9}, end: {line: 1, ch: 9}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "moveH",
                                   [{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 8}, end: {line: 1, ch: 8}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}, primary: true, reversed: false}]);
                  });
  
                  it("should do default move right from multiple cursors if one is inside content", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 14}, end: {line: 2, ch: 14}}],
                                   1, "moveH",
                                   [{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 2}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 15}, end: {line: 2, ch: 15}, primary: true, reversed: false}]);
                  });
  
                  it("should do default backspace from multiple cursors if one is inside content", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 10}, end: {line: 0, ch: 10}},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 2}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "deleteH",
                                   [{start: {line: 0, ch: 9}, end: {line: 0, ch: 9}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}, primary: true, reversed: false}],
                                   "        oe\n       two\n           three\n");
                  });
  
                  it("should do default delete right from multiple cursors if one is inside content", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 15}, end: {line: 2, ch: 15}}],
                                   1, "deleteH",
                                   [{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 15}, end: {line: 2, ch: 15}, primary: true, reversed: false}],
                                   "       one\n       two\n            thre\n"
                                  );
                  });
  
                  it("should collapse ranges and handle other consistent soft tabs when moving left", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 6}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "moveH",
                                   [{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 2}, end: {line: 1, ch: 2}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, primary: true, reversed: false}]);
                  });
  
                  it("should collapse ranges and handle other consistent soft tabs when moving right", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 9}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 9}, end: {line: 2, ch: 9}}],
                                   1, "moveH",
                                   [{start: {line: 0, ch: 9}, end: {line: 0, ch: 9}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 4}, end: {line: 1, ch: 4}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 12}, end: {line: 2, ch: 12}, primary: true, reversed: false}]);
                  });
  
                  it("should delete ranges and do nothing with cursors when backspacing", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}},
                                    {start: {line: 1, ch: 8}, end: {line: 1, ch: 10}},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}}],
                                   -1, "deleteH",
                                   [{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 8}, end: {line: 1, ch: 8}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 10}, end: {line: 2, ch: 10}, primary: true, reversed: false}],
                                   "        one\n        o\n            three\n");
                  });
  
                  it("should delete ranges and do nothing with cursors when deleting right", function () {
                      myEditor.document.setText("        one\n        two\n            three\n");
                      checkSoftTab([{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                    {start: {line: 2, ch: 14}, end: {line: 2, ch: 16}}],
                                   1, "deleteH",
                                   [{start: {line: 0, ch: 5}, end: {line: 0, ch: 5}, primary: false, reversed: false},
                                    {start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: false, reversed: false},
                                    {start: {line: 2, ch: 14}, end: {line: 2, ch: 14}, primary: true, reversed: false}],
                                   "        one\n        two\n            the\n"
                                  );
                  });
  
              });
          });
  
          describe("Smart Tab handling", function () {
              function makeEditor(content, useTabs) {
                  createTestEditor(content, "javascript");
                  var instance = myEditor._codeMirror;
                  instance.setOption("indentWithTabs", useTabs);
                  instance.setOption("indentUnit", 4);
              }
  
              it("should indent and move cursor to correct position if at beginning of an empty line - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "\n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setCursorPos({line: 2, ch: 0});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "        ";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should indent and move cursor to correct position if at beginning of an empty line - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setCursorPos({line: 2, ch: 0});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 2}, end: {line: 2, ch: 2}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "\t\t";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should move cursor to end of whitespace (without adding more) if at beginning of a line with correct amount of whitespace - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "        \n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setCursorPos({line: 2, ch: 0});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, reversed: false});
  
                  expect(myEditor.document.getText()).toEqual(content);
              });
  
              it("should move cursor to end of whitespace (without adding more) if at beginning of a line with correct amount of whitespace - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\t\t\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setCursorPos({line: 2, ch: 0});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 2}, end: {line: 2, ch: 2}, reversed: false});
                  expect(myEditor.document.getText()).toEqual(content);
              });
  
              it("should add another indent whitespace if already past correct indent level on an all whitespace line - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "            \n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setCursorPos({line: 2, ch: 12});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 16}, end: {line: 2, ch: 16}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "    " + lines[2];
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
  
              });
  
              it("should add another indent whitespace if already past correct indent level on an all whitespace line - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\t\t\t\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setCursorPos({line: 2, ch: 3});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 4}, end: {line: 2, ch: 4}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "\t" + lines[2];
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
  
              });
  
              it("should indent improperly indented line to proper level and move cursor to beginning of content if cursor is in whitespace before content - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "    indentme();\n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setCursorPos({line: 2, ch: 2});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "        indentme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should indent improperly indented line to proper level and move cursor to beginning of content if cursor is in whitespace before content - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\tindentme();\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setCursorPos({line: 2, ch: 0});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 2}, end: {line: 2, ch: 2}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "\t\tindentme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add one indent level (not autoindent) if cursor is immediately before content - spaces", function () {
                  var content = "function foo() {\n" +
                      "        if (bar) {\n" +
                      "    indentme();\n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setCursorPos({line: 1, ch: 8});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 1, ch: 12}, end: {line: 1, ch: 12}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[1] = "            if (bar) {";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add one indent level (not autoindent) if cursor is immediately before content - tabs", function () {
                  var content = "function foo() {\n" +
                      "\t\tif (bar) {\n" +
                      "\tindentme();\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setCursorPos({line: 1, ch: 2});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 1, ch: 3}, end: {line: 1, ch: 3}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[1] = "\t\t\tif (bar) {";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should move cursor and not indent further if cursor is in whitespace before properly indented line - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "        indentme();\n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setCursorPos({line: 2, ch: 4});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, reversed: false});
                  expect(myEditor.document.getText()).toEqual(content);
              });
  
              it("should move cursor and not indent further if cursor is in whitespace before properly indented line - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\t\tindentme();\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setCursorPos({line: 2, ch: 1});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 2}, end: {line: 2, ch: 2}, reversed: false});
                  expect(myEditor.document.getText()).toEqual(content);
              });
  
              it("should add an indent level if cursor is immediately before content on properly indented line - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "        indentme();\n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setCursorPos({line: 2, ch: 8});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 12}, end: {line: 2, ch: 12}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "            indentme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add an indent level if cursor is immediately before content on properly indented line - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\t\tindentme();\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setCursorPos({line: 2, ch: 2});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 3}, end: {line: 2, ch: 3}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "\t\t\tindentme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add an indent level to each line (regardless of existing indentation) if selection spans multiple lines - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "    indentme();\n" +
                      "    }\n" +
                      "}",
                      i;
                  makeEditor(content);
                  myEditor.setSelection({line: 1, ch: 6}, {line: 3, ch: 3});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 1, ch: 10}, end: {line: 3, ch: 8}, reversed: false});
  
                  var lines = content.split("\n");
                  for (i = 1; i <= 3; i++) {
                      lines[i] = "    " + lines[i];
                  }
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add an indent level to each line (regardless of existing indentation) if selection spans multiple lines - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\tindentme();\n" +
                      "\t}\n" +
                      "}",
                      i;
                  makeEditor(content, true);
                  myEditor.setSelection({line: 1, ch: 0}, {line: 3, ch: 1});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 1, ch: 0}, end: {line: 3, ch: 2}, reversed: false});
  
                  var lines = content.split("\n");
                  for (i = 1; i <= 3; i++) {
                      lines[i] = "\t" + lines[i];
                  }
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add spaces to indent to the next soft tab stop if cursor is in the middle of a line after non-whitespace content - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "    indentme();\n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setSelection({line: 2, ch: 9}, {line: 2, ch: 9}); // should add three spaces to get to column 12
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 12}, end: {line: 2, ch: 12}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "    inden   tme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should insert a tab if cursor is in the middle of a line after non-whitespace content - tab", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\tindentme();\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setSelection({line: 2, ch: 5}, {line: 2, ch: 5});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 6}, end: {line: 2, ch: 6}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "\tinde\tntme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add spaces to next soft tab before the beginning of the selection if it's a range in the middle of a line after non-whitespace content - spaces", function () {
                  var content = "function foo() {\n" +
                      "    if (bar) {\n" +
                      "    indentme();\n" +
                      "    }\n" +
                      "}";
                  makeEditor(content);
                  myEditor.setSelection({line: 2, ch: 9}, {line: 2, ch: 14}); // should add three spaces to get to column 12
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 12}, end: {line: 2, ch: 17}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "    inden   tme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              it("should add a tab before the beginning of the selection if it's a range in the middle of a line after non-whitespace content - tabs", function () {
                  var content = "function foo() {\n" +
                      "\tif (bar) {\n" +
                      "\tindentme();\n" +
                      "\t}\n" +
                      "}";
                  makeEditor(content, true);
                  myEditor.setSelection({line: 2, ch: 5}, {line: 2, ch: 8});
                  myEditor._handleTabKey();
                  expectSelection({start: {line: 2, ch: 6}, end: {line: 2, ch: 9}, reversed: false});
  
                  var lines = content.split("\n");
                  lines[2] = "\tinde\tntme();";
                  expect(myEditor.document.getText()).toEqual(lines.join("\n"));
              });
  
              describe("with multiple selections", function () {
                  // In some of these tests we force a selection other than the last to be primary if the last selection is the
                  // one that triggers the behavior - so our code can't just cheat and rely on the primary selection.
  
                  // Note that a side-effect of the way CM adds indent levels is that ends of ranges that are within the
                  // whitespace at the beginning of the line get pushed to the first non-whitespace character on the line,
                  // so in tests below that fall back to "add one indent level before each line", the selections might change
                  // more than you would expect by just adding a single indent level.
  
                  it("should add one indent level before all selected lines if any of the selections is multiline - spaces", function () {
                      var content = "function foo() {\n" +
                          "    if (bar) {\n" +
                          "    indentme();\n" +
                          "    }\n" +
                          "}";
                      makeEditor(content);
                      myEditor.setSelections([{start: {line: 0, ch: 9}, end: {line: 0, ch: 9}, primary: true},
                                              {start: {line: 2, ch: 6}, end: {line: 3, ch: 3}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 0, ch: 13}, end: {line: 0, ch: 13}, primary: true, reversed: false},
                                                                {start: {line: 2, ch: 10}, end: {line: 3, ch: 8}, primary: false, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[0] = "    " + lines[0];
                      lines[2] = "    " + lines[2];
                      lines[3] = "    " + lines[3];
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should add one indent level before all selected lines if any of the selections is multiline - tabs", function () {
                      var content = "function foo() {\n" +
                          "\tif (bar) {\n" +
                          "\tindentme();\n" +
                          "\t}\n" +
                          "}";
                      makeEditor(content, true);
                      myEditor.setSelections([{start: {line: 0, ch: 6}, end: {line: 0, ch: 6}, primary: true},
                                              {start: {line: 2, ch: 3}, end: {line: 3, ch: 1}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 0, ch: 7}, end: {line: 0, ch: 7}, primary: true, reversed: false},
                                                                {start: {line: 2, ch: 4}, end: {line: 3, ch: 2}, primary: false, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[0] = "\t" + lines[0];
                      lines[2] = "\t" + lines[2];
                      lines[3] = "\t" + lines[3];
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should add spaces before each cursor to get to next tab stop if any selection is after first non-whitespace character in its line - spaces", function () {
                      var content = "function foo() {\n" +
                          "    if (bar) {\n" +
                          "    indentme();\n" +
                          "    }\n" +
                          "}";
                      makeEditor(content);
                      myEditor.setSelections([{start: {line: 0, ch: 3}, end: {line: 0, ch: 3}},
                                              {start: {line: 2, ch: 6}, end: {line: 2, ch: 6}},
                                              {start: {line: 3, ch: 2}, end: {line: 3, ch: 2}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}, primary: false, reversed: false},
                                                                {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, primary: false, reversed: false},
                                                                {start: {line: 3, ch: 4}, end: {line: 3, ch: 4}, primary: true, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[0] = "fun ction foo() {";
                      lines[2] = "    in  dentme();";
                      lines[3] = "      }";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should add a tab before each cursor if any selection is after first non-whitespace character in its line - tabs", function () {
                      var content = "function foo() {\n" +
                          "\tif (bar) {\n" +
                          "\tindentme();\n" +
                          "\t}\n" +
                          "}";
                      makeEditor(content, true);
                      myEditor.setSelections([{start: {line: 0, ch: 3}, end: {line: 0, ch: 3}},
                                              {start: {line: 2, ch: 6}, end: {line: 2, ch: 6}},
                                              {start: {line: 3, ch: 1}, end: {line: 3, ch: 1}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 0, ch: 4}, end: {line: 0, ch: 4}, primary: false, reversed: false},
                                                                {start: {line: 2, ch: 7}, end: {line: 2, ch: 7}, primary: false, reversed: false},
                                                                {start: {line: 3, ch: 2}, end: {line: 3, ch: 2}, primary: true, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[0] = "fun\tction foo() {";
                      lines[2] = "\tinden\ttme();";
                      lines[3] = "\t\t}";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should add spaces before beginning of each range to get to next tab stop if any selection is after first non-whitespace character in its line - spaces", function () {
                      var content = "function foo() {\n" +
                          "    if (bar) {\n" +
                          "    indentme();\n" +
                          "    }\n" +
                          "}";
                      makeEditor(content);
                      myEditor.setSelections([{start: {line: 0, ch: 3}, end: {line: 0, ch: 6}},
                                              {start: {line: 2, ch: 6}, end: {line: 2, ch: 9}},
                                              {start: {line: 3, ch: 2}, end: {line: 3, ch: 4}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 0, ch: 4}, end: {line: 0, ch: 7}, primary: false, reversed: false},
                                                                {start: {line: 2, ch: 8}, end: {line: 2, ch: 11}, primary: false, reversed: false},
                                                                {start: {line: 3, ch: 4}, end: {line: 3, ch: 6}, primary: true, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[0] = "fun ction foo() {";
                      lines[2] = "    in  dentme();";
                      lines[3] = "      }";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should add a tab before beginning of each range if any selection is after first non-whitespace character in its line - tabs", function () {
                      var content = "function foo() {\n" +
                          "\tif (bar) {\n" +
                          "\tindentme();\n" +
                          "\t}\n" +
                          "}";
                      makeEditor(content, true);
                      myEditor.setSelections([{start: {line: 0, ch: 3}, end: {line: 0, ch: 6}},
                                              {start: {line: 2, ch: 6}, end: {line: 2, ch: 9}},
                                              {start: {line: 3, ch: 1}, end: {line: 3, ch: 2}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 0, ch: 4}, end: {line: 0, ch: 7}, primary: false, reversed: false},
                                                                {start: {line: 2, ch: 7}, end: {line: 2, ch: 10}, primary: false, reversed: false},
                                                                {start: {line: 3, ch: 2}, end: {line: 3, ch: 3}, primary: true, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[0] = "fun\tction foo() {";
                      lines[2] = "\tinden\ttme();";
                      lines[3] = "\t\t}";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should add spaces before each cursor to get to next tab stop (not autoindent) if any selection is exactly before the first non-whitespace character on the line - spaces", function () {
                      var content = "function foo() {\n" +
                          "    if (bar) {\n" +
                          "    indentme();\n" +
                          "    }\n" +
                          "}";
                      makeEditor(content);
                      myEditor.setSelections([{start: {line: 1, ch: 4}, end: {line: 1, ch: 4}, primary: true}, // should not move
                                              {start: {line: 2, ch: 4}, end: {line: 2, ch: 4}}]); // should get indented and move
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 1, ch: 8}, end: {line: 1, ch: 8}, primary: true, reversed: false},
                                                                {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, primary: false, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[1] = "        if (bar) {";
                      lines[2] = "        indentme();";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should add a tab at each cursor (not autoindent) if any selection is exactly before the first non-whitespace character on the line - tabs", function () {
                      var content = "function foo() {\n" +
                          "\tif (bar) {\n" +
                          "\tindentme();\n" +
                          "\t}\n" +
                          "}";
                      makeEditor(content, true);
                      myEditor.setSelections([{start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: true}, // should not move
                                              {start: {line: 2, ch: 1}, end: {line: 2, ch: 1}}]); // should get indented and move
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 1, ch: 2}, end: {line: 1, ch: 2}, primary: true, reversed: false},
                                                                {start: {line: 2, ch: 2}, end: {line: 2, ch: 2}, primary: false, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[1] = "\t\tif (bar) {";
                      lines[2] = "\t\tindentme();";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should try to autoindent each line if all cursors are in start-of-line whitespace, and if at least one cursor changed position or indent was added, do nothing further - spaces", function () {
                      var content = "function foo() {\n" +
                          "    if (bar) {\n" +
                          "    indentme();\n" +
                          "    }\n" +
                          "}";
                      makeEditor(content);
                      myEditor.setSelections([{start: {line: 1, ch: 2}, end: {line: 1, ch: 2}, primary: true}, // should not move
                                              {start: {line: 2, ch: 2}, end: {line: 2, ch: 2}}]); // should get indented and move
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 1, ch: 4}, end: {line: 1, ch: 4}, primary: true, reversed: false},
                                                                {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}, primary: false, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[2] = "        indentme();";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should try to autoindent each line if all cursors are in start-of-line whitespace, and if at least one cursor changed position or indent was added, do nothing further - tabs", function () {
                      var content = "function foo() {\n" +
                          "\tif (bar) {\n" +
                          "\tindentme();\n" +
                          "\t}\n" +
                          "}";
                      makeEditor(content, true);
                      myEditor.setSelections([{start: {line: 1, ch: 0}, end: {line: 1, ch: 0}, primary: true}, // should not move
                                              {start: {line: 2, ch: 0}, end: {line: 2, ch: 0}}]); // should get indented and move
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 1, ch: 1}, end: {line: 1, ch: 1}, primary: true, reversed: false},
                                                                {start: {line: 2, ch: 2}, end: {line: 2, ch: 2}, primary: false, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[2] = "\t\tindentme();";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should try to autoindent each line if all cursors are in start-of-line whitespace, but if no cursors changed position or added indent, add an indent to the beginning of each line - spaces", function () {
                      var content = "function foo() {\n" +
                          "    if (bar) {\n" +
                          "        indentme();\n" + // indent already correct
                          "    }\n" +
                          "}";
                      makeEditor(content);
                      myEditor.setSelections([{start: {line: 1, ch: 4}, end: {line: 1, ch: 4}},
                                              {start: {line: 2, ch: 8}, end: {line: 2, ch: 8}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 1, ch: 8}, end: {line: 1, ch: 8}, primary: false, reversed: false},
                                                                {start: {line: 2, ch: 12}, end: {line: 2, ch: 12}, primary: true, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[1] = "        if (bar) {";
                      lines[2] = "            indentme();";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
  
                  it("should try to autoindent each line if all cursors are in start-of-line whitespace, but if no cursors changed position or added indent, add an indent to the beginning of each line - tabs", function () {
                      var content = "function foo() {\n" +
                          "\tif (bar) {\n" +
                          "\t\tindentme();\n" + // indent already correct
                          "\t}\n" +
                          "}";
                      makeEditor(content, true);
                      myEditor.setSelections([{start: {line: 1, ch: 1}, end: {line: 1, ch: 1}},
                                              {start: {line: 2, ch: 2}, end: {line: 2, ch: 2}}]);
                      myEditor._handleTabKey();
                      expectSelections([{start: {line: 1, ch: 2}, end: {line: 1, ch: 2}, primary: false, reversed: false},
                                                                {start: {line: 2, ch: 3}, end: {line: 2, ch: 3}, primary: true, reversed: false}]);
  
                      var lines = content.split("\n");
                      lines[1] = "\t\tif (bar) {";
                      lines[2] = "\t\t\tindentme();";
                      expect(myEditor.document.getText()).toEqual(lines.join("\n"));
                  });
              });
          });
          
          describe("Gutter APIs", function () {
              var leftGutter = "left",
                  rightGutter = "right",
                  lineNumberGutter = "CodeMirror-linenumbers";
                  
              beforeEach(function () {
                  createTestEditor(defaultContent, "javascript");
                  Editor.registerGutter(leftGutter, 1);
                  Editor.registerGutter(rightGutter, 101);
              });
              
              afterEach(function () {
                  var nonLineNumberGutters = Editor.getRegisteredGutters().map(function (gutter) {
                      return gutter.name;
                  });
                  nonLineNumberGutters.forEach(function (gutter) {
                      if (gutter !== lineNumberGutter) {
                          Editor.unregisterGutter(gutter);
                      }
                  });
              });
              
              it("should register multiple gutters in the correct order", function () {
                  var expectedGutters = [leftGutter, lineNumberGutter, rightGutter];
                  var gutters  = myEditor._codeMirror.getOption("gutters");
                  var registeredGutters = Editor.getRegisteredGutters().map(function (gutter) {
                      return gutter.name;
                  });
                  expect(gutters).toEqual(expectedGutters);
                  expect(registeredGutters).toEqual(expectedGutters);
              });
              
              it("should return gutters registered with the same priority in insertion order", function () {
                  var secondRightGutter = "second-right";
                  Editor.registerGutter(secondRightGutter, 101);
                  var expectedGutters = [leftGutter, lineNumberGutter, rightGutter, secondRightGutter];
                  var gutters  = myEditor._codeMirror.getOption("gutters");
                  var registeredGutters = Editor.getRegisteredGutters().map(function (gutter) {
                      return gutter.name;
                  });
                  expect(gutters).toEqual(expectedGutters);
                  expect(registeredGutters).toEqual(expectedGutters);
              });
              
              it("should have only gutters registered with the intended languageIds ", function () {
                  var lessOnlyGutter = "less-only-gutter";
                  Editor.registerGutter(lessOnlyGutter, 101, ["less"]);
                  var expectedGutters = [leftGutter, lineNumberGutter, rightGutter];
                  var expectedRegisteredGutters = [leftGutter, lineNumberGutter, rightGutter, lessOnlyGutter];
                  var gutters  = myEditor._codeMirror.getOption("gutters");
                  var registeredGutters = Editor.getRegisteredGutters().map(function (gutter) {
                      return gutter.name;
                  });
                  expect(gutters).toEqual(expectedGutters);
                  expect(registeredGutters).toEqual(expectedRegisteredGutters);
              });
              
              it("should unregister gutters correctly", function () {
                  Editor.unregisterGutter(leftGutter);
                  Editor.unregisterGutter(rightGutter);
                  Editor.registerGutter(leftGutter, 1);
                  var expectedGutters = [leftGutter, lineNumberGutter];
                  var gutters  = myEditor._codeMirror.getOption("gutters");
                  var registeredGutters = Editor.getRegisteredGutters().map(function (gutter) {
                      return gutter.name;
                  });
                  expect(gutters).toEqual(expectedGutters);
                  expect(registeredGutters).toEqual(expectedGutters);
              });
              
              it("should set gutter marker correctly", function () {
                  var marker = window.document.createElement("div");
                  myEditor.setGutterMarker(1, leftGutter, marker);
                  var lineInfo = myEditor._codeMirror.lineInfo(1);
                  expect(lineInfo.gutterMarkers[leftGutter], marker);
              });
          });
      });
  });
  