diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 60b522d39a4..8c289c996f8 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -375,65 +375,131 @@ define(function (require, exports, module) { /** * @private - * Handle Tab key press. - * @param {!CodeMirror} instance CodeMirror instance. + * Helper function for `_handleTabKey()` (case 2) - see comment in that function. + * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>} selections + * The selections to indent. */ - Editor.prototype._handleTabKey = function () { - // Tab key handling is done as follows: - // 1. If the selection is before any text and the indentation is to the left of - // the proper indentation then indent it to the proper place. Otherwise, - // add another tab. In either case, move the insertion point to the - // beginning of the text. - // 2. If the selection is multi-line, indent all the lines. - // 3. If the selection is after the first non-space character, and is an - // insertion point, insert a tab character or the appropriate number - // of spaces to pad to the nearest tab boundary. + Editor.prototype._addIndentAtEachSelection = function (selections) { var instance = this._codeMirror, - from = this.getCursorPos(false, "start"), - to = this.getCursorPos(false, "end"), - line = instance.getLine(from.line), - indentAuto = false, - insertTab = false; + usingTabs = instance.getOption("indentWithTabs"), + indentUnit = instance.getOption("indentUnit"), + edits = []; - if (from.line === to.line) { - if (line.search(/\S/) > to.ch || to.ch === 0) { - indentAuto = true; - } - } - - if (indentAuto) { - var currentLength = line.length; - CodeMirror.commands.indentAuto(instance); - - // If the amount of whitespace and the cursor position didn't change, we must have - // already been at the correct indentation level as far as CM is concerned, so insert - // another tab. - if (instance.getLine(from.line).length === currentLength) { - var newFrom = this.getCursorPos(false, "start"), - newTo = this.getCursorPos(false, "end"); - if (newFrom.line === from.line && newFrom.ch === from.ch && - newTo.line === to.line && newTo.ch === to.ch) { - insertTab = true; - to.ch = 0; + _.each(selections, function (sel) { + var indentStr = "", i, numSpaces; + if (usingTabs) { + indentStr = "\t"; + } else { + numSpaces = indentUnit - (sel.start.ch % indentUnit); + for (i = 0; i < numSpaces; i++) { + indentStr += " "; } } - } else if (instance.somethingSelected() && from.line !== to.line) { - CodeMirror.commands.indentMore(instance); + edits.push({edit: {text: indentStr, start: sel.start}}); + }); + + this.document.doMultipleEdits(edits); + }; + + /** + * @private + * Helper function for `_handleTabKey()` (case 3) - see comment in that function. + * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>} selections + * The selections to indent. + */ + Editor.prototype._autoIndentEachSelection = function (selections) { + // Capture all the line lengths, so we can tell if anything changed. + // Note that this function should only be called if all selections are within a single line. + var instance = this._codeMirror, + lineLengths = {}; + _.each(selections, function (sel) { + lineLengths[sel.start.line] = instance.getLine(sel.start.line).length; + }); + + // First, try to do a smart indent on all selections. + CodeMirror.commands.indentAuto(instance); + + // If there were no code or selection changes, then indent each selection one more indent. + var changed = false, + newSelections = this.getSelections(); + if (newSelections.length === selections.length) { + _.each(selections, function (sel, index) { + var newSel = newSelections[index]; + if (CodeMirror.cmpPos(sel.start, newSel.start) !== 0 || + CodeMirror.cmpPos(sel.end, newSel.end) !== 0 || + instance.getLine(sel.start.line).length !== lineLengths[sel.start.line]) { + changed = true; + // Bail - we don't need to look any further once we've found a change. + return false; + } + }); } else { - insertTab = true; + changed = true; } - if (insertTab) { - if (instance.getOption("indentWithTabs")) { - CodeMirror.commands.insertTab(instance); - } else { - var i, ins = "", numSpaces = instance.getOption("indentUnit"); - numSpaces -= from.ch % numSpaces; - for (i = 0; i < numSpaces; i++) { - ins += " "; - } - instance.replaceSelection(ins, "end"); + if (!changed) { + CodeMirror.commands.indentMore(instance); + } + }; + + /** + * @private + * Handle Tab key press. + * @param {!CodeMirror} instance CodeMirror instance. + */ + Editor.prototype._handleTabKey = function () { + // Tab key handling is done as follows: + // 1. If any of the selections are multiline, just add one indent level to the + // beginning of all lines that intersect any selection. + // 2. Otherwise, if any of the selections is a cursor or single-line range that + // ends at or after the first non-whitespace character in a line: + // - if indentation is set to tabs, just insert a hard tab before each selection. + // - if indentation is set to spaces, insert the appropriate number of spaces before + // each selection to get to its next soft tab stop. + // 3. Otherwise (all selections are cursors or single-line, and are in the whitespace + // before their respective lines), try to autoindent each line based on the mode. + // If none of the cursors moved and no space was added, then add one indent level + // to the beginning of all lines. + + // Note that in case 2, we do the "dumb" insertion even if the cursor is immediately + // before the first non-whitespace character in a line. It might seem more convenient + // to do autoindent in that case. However, the problem is if that line is already + // indented past its "proper" location. In that case, we don't want Tab to + // *outdent* the line. If we had more control over the autoindent algorithm or + // implemented it ourselves, we could handle that case separately. + + var instance = this._codeMirror, + selectionType = "indentAuto", + selections = this.getSelections(); + + _.each(selections, function (sel) { + if (sel.start.line !== sel.end.line) { + // Case 1 - we found a multiline selection. We can bail as soon as we find one of these. + selectionType = "indentAtBeginning"; + return false; + } else if (sel.end.ch >= instance.getLine(sel.end.line).search(/\S/)) { + // Case 2 - we found a selection that ends at or after the first non-whitespace + // character on the line. We need to keep looking in case we find a later multiline + // selection though. + selectionType = "indentAtSelection"; } + }); + + switch (selectionType) { + case "indentAtBeginning": + // Case 1 + CodeMirror.commands.indentMore(instance); + break; + + case "indentAtSelection": + // Case 2 + this._addIndentAtEachSelection(selections); + break; + + case "indentAuto": + // Case 3 + this._autoIndentEachSelection(selections); + break; } }; diff --git a/test/spec/Editor-test.js b/test/spec/Editor-test.js index f83b855b450..08c45be5c3d 100644 --- a/test/spec/Editor-test.js +++ b/test/spec/Editor-test.js @@ -1315,5 +1315,504 @@ define(function (require, exports, module) { }); }); + + 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 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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({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(); + expect(myEditor.getSelection()).toEqual({start: {line: 1, ch: 2}, 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" + + "}", + i; + makeEditor(content); + myEditor.setSelection({line: 2, ch: 9}, {line: 2, ch: 9}); // should add three spaces to get to column 12 + myEditor._handleTabKey(); + expect(myEditor.getSelection()).toEqual({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" + + "}", + i; + makeEditor(content, true); + myEditor.setSelection({line: 2, ch: 5}, {line: 2, ch: 5}); + myEditor._handleTabKey(); + expect(myEditor.getSelection()).toEqual({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" + + "}", + i; + makeEditor(content); + myEditor.setSelection({line: 2, ch: 9}, {line: 2, ch: 14}); // should add three spaces to get to column 12 + myEditor._handleTabKey(); + expect(myEditor.getSelection()).toEqual({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" + + "}", + i; + makeEditor(content, true); + myEditor.setSelection({line: 2, ch: 5}, {line: 2, ch: 8}); + myEditor._handleTabKey(); + expect(myEditor.getSelection()).toEqual({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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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" + + "}", + i; + 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(); + expect(myEditor.getSelections()).toEqual([{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")); + }); + }); + }); }); });